So I was unsuccessful at hooking up the Session-based wizard from django-merlin, but I am trying again with the wizard that is included in the django source. However, when trying to upload files using ImageField, it seems that the files request.FILES are not being bound to the form, because after trying to upload a file I get a "This Field is Required" error. Here is what I have:
forms.py:
class StepOneForm(forms.Form):
year = forms.ChoiceField(choices=YEAR_CHOICES)
...
class StepTwoForm(forms.Form):
main_image = forms.ImageField()
...
class CreateWizard(SessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT))
def done(self, form_list, **kwargs):
d['main_image'] = request.FILES['main_image']
db = Thing(**d)
db.save()
return render(request, 'wizard-done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
In the CreateWizard above I have tried to save the main_image in the done method as was discussed in this stackoverflow question, but I have not been successful.
UPDATE:
Adding enctype=multipart/form-data has allowed me to bind the file, but now I am getting a new error:
global name 'request' is not defined
even though the request context processor is in my TEMPLATE_CONTEXT_PROCESSORS. Defining the done method like in the linked stackoverflow post did not work either:
class CreateWizard(SessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT))
def done(self, request, form_list):
results in a TypeError: done expects 3 arguments, 2 given
You will need self.request:
return render(self.request, 'wizard-done.html', {
'form_data': [form.cleaned_data for form in form_list], })
Related
I am working with a CBV that uses 2 ModelForm instances. I would like to display the individual form errors. It seems like this is a little challenging when using multiple forms in a class based view.
Heres a smaller snippet to show what I am working with...
class EmployeeCreate(CreateView):
form_class = EmployeeCreateForm
form_class_2 = AddressCreateForm
def post(self, request, *args, **kwargs):
employee_form = self.form_class(request.POST)
address_form = self.form_class_2(request.POST)
# Make sure both forms are validated
if employee_form.is_valid() and address_form.is_valid():
employee = employee_form.save(commit=False)
address = address_form.save(commit=False)
employee.parent = self.request.user
employee.save()
address.user = employee
address.save()
return JsonResponse({'message': 'Employee created successfully.'}, status=200)
else:
return self.form_invalid(**kwargs)
def get_context_data(self, **kwargs):
# render both forms to create an Account, and Address
context = super(EmployeeCreateView, self).get_context_data()
context['employee_form'] = self.form_class
context['address_form'] = self.form_class_2
return context
def form_invalid(self, **kwargs):
return JsonResponse({'success': False})
Now when the form is invalid, the form_invalid method is getting called and returning the JsonResponse message, but I would much rather return the specific form error.
I am trying to find a way to display each individual form error for the employee_form and the address_form. Is there a possible way to do this override in the form_invalid method?
Thank you in advance!
you are returning both forms error in single JsonResponse. Instead you should return different forms error in single JsonResponse like
return JsonResponse({'employee_form_errors': self.form_invalid(employee_form),
'address_form_errors': self.form_invalid(address_form) }, status=400)
you should use individually use form_invalid with both forms.
I am trying to create a user profile page where users can see and update their preferences for certain things, like whether they are vegetarian, or have a particular allergy, etc. I want the data to be displayed as a form, with their current preferences already populating the form fields.
So I've created the following Model:
class FoodPreferences(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE) # One user has one set of food prefs
vegetarian = models.BooleanField()
vegan = models.BooleanField()
...
that's referenced in my forms.py:
class FoodPreferencesForm(forms.ModelForm):
class Meta:
model = FoodPreferences
exclude = ('user', )
I've tried creating a view that inherits FormView and then referencing the form, like this:
class UserProfileView(generic.FormView):
template_name = "registration/profile.html"
form_class = FoodPreferencesForm
success_url = reverse_lazy('user_profile')
This saves the form to a instance of the model correctly, but obviously it just displays the blank form again, after updating, so the user has no idea what their current preferences are.
To implement this I thought I might need to override get() and post() to get the instance of FoodPreferences for the user, and then pass those values into the form like you would a request.POST object. However, firstly, I don't know how to do that, and secondly I'd be taking responsibility for correctly updating the database, which the FormView was already doing.
This is what I've got for that solution:
def get(self, request, *args, **kwargs):
prefs = FoodPreferences.objects.get(user=request.user)
form = self.form_class(prefs)
return render(request, self.template_name, {'form': form, })
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if not form.is_valid():
return render(request, self.template_name, {'form': form, 'error': 'Something went wrong.'})
curr_prefs = FoodPreferences.objects.update_or_create(form.fields)
prefs.save()
return render(request, self.template_name, {'form': form, })
but I get a TypeError: argument of type 'FoodPreferences' is not iterable on the line in get():
form = self.form_class(prefs)
because it's not expecting a model instance.
Am I thinking about this in the right way? This seems like a common enough problem that Django would have something inbuilt to do it, but I can't find anything.
You should only rarely need to define get or post in a class-based view, and you definitely don't here.
To start with, you need to use a more appropriate base class for your view. Here you want to update an existing item, so you should use UpdateView.
Secondly, you need to tell the class how to get the existing object to update, which you can do by definining get_object. So:
class UserProfileView(generic.UpdateView):
template_name = "registration/profile.html"
form_class = FoodPreferencesForm
success_url = reverse_lazy('user_profile')
def get_object(self, queryset=None):
return self.request.user.foodpreferences
# or, if you aren't certain that the object already exists:
obj, _ = FoodPreferences.objects.get_or_create(user=self.request.user)
return obj
THE CONTEXT
I am trying to implement a tagging system for my project. The various plug-in solutions (taggit, tagulous) are each unsuitable in some way.
I would like to allow users to select from existing tags or create new ones in a Select2 tagging field. Existing tags can be added or removed without problem. My difficulty is in the dynamic generation and assignment of new tags.
MY APPROACH
Select2 helpfully renders the manually-entered tags differently in the DOM from those picked from the database via autocomplete. So upon clicking submit, I have javascript collect the new tags and string them together in the value of a hidden input, then delete them from the Select2 field to avoid any validation errors (the form otherwise POSTs the tag names as the ids, which throws a db error).
In the view, I iterate over the desired new tags. For each entry I create the new tag, then add it to the parent object's related set.
THE PROBLEM
While this successfully creates each tag (verified via Admin) it doesn't add it to the related set.
No errors are generated on the (clearly not succeeding) related set add.
The newly-generated tags are correctly instantiated and can be Select2-chosen and sucessfully assigned on a subsequent UpdateView, so I'm certain the problem lies in the view-assignment of the tags to the parent.
The same code executed via the Django shell work flawlessly, so I don't believe its a simple syntax error.
Thus the locus of the problem seems to be in the POST view code adding newly-generated tags to the parent, but I cannot see where the code goes astray.
Thanks for any insights or advice!
models.py:
class Recipe_tag(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
tag = models.CharField('Tag name',max_length=32,null=False,unique=True)
def __str__(self):
return str(self.tag)
class Recipe_base(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
name = models.CharField('Recipe name',max_length=128,null=False)
tags = models.ManyToManyField(Recipe_tag,related_name='recipes',null=True,blank=True)
def __str__(self):
return str(self.name)
The post portion of the view:
def post(self, request, *args, **kwargs):
self.object = None
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
form = RecipeUpdateTagsForm(request.POST,instance=r)
form_valid = form.is_valid()
if form_valid:
if form.has_changed:
f = form.save(commit=False)
clean = form.cleaned_data
f.addedTags = clean['addedTags']
if f.addedTags == 'placeholder':
pass
else:
new_tags = f.addedTags.split(',')
for new_tag in new_tags:
a = Recipe_tag(tag=new_tag)
a.save()
r.tags.add(a)
f.save()
form.save_m2m()
else:
pass
return self.form_valid(form)
else:
return self.form_invalid(form)
Doing further digging, I found a post on another site in which the OP was experiencing the same issues. The trick is to remove the m2m assignment from the "create" or "update" process entirely, because the final save() that occurs in form_valid will discard any changes to the parent's related set.
In my case, the solution was to punt the iteration/assignment of the new tags to form_valid, directly after the final save() occurs there:
def form_valid(self, form, **kwargs):
self.object = form.save()
addedTags = kwargs['addedTags']
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
if addedTags == 'placeholder':
pass
else:
new_tags = addedTags.split(',')
for new_tag in new_tags:
a = Recipe_tag(tag=new_tag)
a.save()
# print("debug // new tag: %s, %s" % (a.tag, a.id))
r.tags.add(a)
# print("debug // added %s to %s" % (a.tag,r.id))
return HttpResponseRedirect(self.get_success_url())
def get(self, request, *args, **kwargs):
self.object = Recipe_base.objects.get(id=self.kwargs.get('pk'))
recipe_name = self.object.name
recipe_id = self.kwargs.get('pk')
form = RecipeUpdateForm(instance=Recipe_base.objects.get(id=self.kwargs.get('pk')))
return self.render_to_response(self.get_context_data(form=form,recipe_name=recipe_name,recipe_id=recipe_id))
def post(self, request, *args, **kwargs):
self.object = None
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
form = RecipeUpdateForm(request.POST,instance=r)
form_valid = form.is_valid()
#print("debug // form_valid PASSED")
if form_valid:
if form.has_changed:
#print("debug // Form changed...")
f = form.save(commit=False)
#print("debug // Passes save C=F")
clean = form.cleaned_data
#print('debug // pre-existing tags: ',clean['tags'])
f.addedTags = clean['addedTags']
addedTags = clean['addedTags']
#print('debug // manual tags: ',clean['addedTags'])
f.save()
form.save_m2m()
#print("debug // Form saved")
else:
#print("debug // Form unchanged, skipping...")
pass
#print("debug // Reached successful return")
return self.form_valid(form, addedTags=addedTags,pk=r.id)
else:
#print("debug // Form fails validation")
#print("debug // Reached unsuccessful return")
return self.form_invalid(form)
If I use the template_name variable, my SessionWizardView works like a pycharm!
I am having troubles with the get_template_names() method provided by the SessionWizardView.
My first template using the get_template_names() method renders perfectly displaying the correct url.
http://127.0.0.1:8000/wow/character_creation/
I submit the form, and my second form using the get_template_name() method renders perfectly displaying the correct url and form.
http://127.0.0.1:8000/wow/character_creation/
I submit my second form, or if I press the prev step or first step, the following url is displayed
http://127.0.0.1:8000/character_creation/
Here is the error message:
Page not found (404)
Request Method: POST
Request URL: http://127.0.0.1:8000/character_creation/
Anyone having a reason why the /wow/ part of my url has been removed when submitting the second form? Is there a bug in the get_template_names() method?
Here is my views.py
FORMS = [
("0", wow.forms.CharacterCreationForm1),
("1", wow.forms.CharacterCreationForm2),
("2", wow.forms.CharacterCreationForm3),
]
TEMPLATES = {
"0": "wow/character_creation_form_1.html",
"1": "wow/character_creation_form_2.html",
"2": "wow/character_creation_form_3.html",
}
class CharacterWizard(SessionWizardView):
instance = None
#template_name = "wow/character_creation_form_1.html"
# Requires the user to be logged in for every instance of the form wizard
# as-view() *urls.py* creates an instance called dispatch(). Dispatch is used to relay information
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
self.instance = CharacterCreation()
return super(CharacterWizard, self).dispatch(request, *args, **kwargs)
def get_form_instance(self, step):
return self.instance
def get_template_names(self):
#Custom templates for the different steps
return [TEMPLATES[self.steps.current]]
def done(self, form_list, **kwargs):
self.instance.character_creation_user = self.request.user
self.instance.save()
return HttpResponseRedirect('/wow/home/')
Here is my url.py
url(r'^character_creation/$', CharacterWizard.as_view(FORMS), name='character_creation'),
Thank you guys!
In my second and third templates, my form is now action="/character_creation/" instead of /wow/character_creation/.
I am using django vanilla views (https://github.com/tomchristie/django-vanilla-views)
to edit a model object (the Model CreateView), this model has an imagefield.
but on create it does not seem to save the image, maybe because my not standard usage.
the are a few things:
for the model ImageField I am using a upload_to function to save the image to a specific dir
The create is done with some information not to be filled in by a user, so I Am using a dispatch to get the related object from the url
before saving, in the form_valid function, I use form.save(commit=False) than do some things before saving it and afterwards save the object
the code:
the function used for the upload_to
def get_image_path_albumphoto(instance, filename):
return os.path.join('albums', slugify(str(instance.album)), filename)
the model (at least the significant fields):
class AlbumPhoto(models.Model):
.... some fields ...
album = models.ForeignKey(Album, blank=False, null=False)
image = models.ImageField(upload_to=get_image_path_albumphoto, blank=True, null=True)
the create view:
class AlbumPhotoCreate(CreateView):
model = AlbumPhoto
fields=('all the other fields except the album','image')
def dispatch(self, *args, **kwargs):
self.album = get_object_or_none(Album, id=kwargs['album_id'])
return super(AlbumPhotoCreate, self).dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
kwargs['album'] = self.album
return kwargs
def get_form(self, data=None, files=None, **kwargs):
initial={'some_field':'gets_initialized here'}
kwargs['initial']=initial
return super(AlbumPhotoCreate, self).get_form(data,files, **kwargs)
def get_success_url(self):
if self.album:
return 'an url using the album id with %d' % self.album.id
return reverse_lazy('albumphoto_list')
def form_valid(self, form):
obj = form.save(commit=False)
obj.album=self.album
obj.save()
success_url= 'some url with the object id as %d' % obj.id
return HttpResponseRedirect(success_url)
but the image is never saved using this code.... it works when using the django admin to add the object,,, so it's something in using this CreateView
update
I tried to reverse my code first include album in the fields, removed the dispatch and form_valid functions... no succes... finally the intialising removed... (get_form) no succes as well... and this is parctically the default usage of CreateView... so it's probably something in the upload_to function... (?)
The answer for this problem was more generic than this question was formed:
The vanilla update view was an extended version of the GenericModelView, so this problem was more generic, and not caused by the django vanilla view.
To upload a file using a form view, the form tag in the template should have:
the enctype="multipart/form-data"
e.g.:
<form method="POST" action="." enctype="multipart/form-data">