Im trying to use one form to get data for two different models, to do so, I changed my create view get_context_data function like this:
class tarea_crear(CreateView):
template_name = "crud_tareas/tarea_crear.html"
form_class = tareaForm
success_url = reverse_lazy('tareas:crear-tarea')
def get_context_data(self, **kwargs):
context = super(tarea_crear, self).get_context_data(**kwargs)
context['form'] = {'audiosForm':audiosForm,'tareaForm':tareaForm}
return context
This allowed me to call both forms on my template, like this: (Dont worry, I remembered the crfs tag and the form)
{{form.tareaForm.as_p}}
{{form.audiosForm.as_p}}
<input type="submit" value="Crear Tarea">
I also needed to validate some informationfrom the first form, and I did it like this in the view:
def form_valid(self, form):
titulo = form.cleaned_data['titulo']
if not any(i.isdigit() for i in titulo):
return super(tarea_crear, self).form_valid(self, form)
return super(tarea_crear, self).form_invalid(self, form)
The problem is that when I do the POST of the form, only the first half of the data is actually sent, I know this because I cant call form.cleaned_data['audios'] in the view without getting an error. Any idea how to handle a View with two forms?
Related
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
I'm working on simple expense tracking app. Below you can find the view with all user's Operations (expense or income):
Based on this thread I implemented bootstrap modal window to display new operation form:
Below you can find ManageOperations view which is responsible for displaying views presented above:
class ManageOperations(ListView, FormView, OperationMixIn):
model = Operation
form_class = OperationForm
template_name = "expenses/manage_operations.html"
success_url = reverse_lazy('manage_operations')
def get_context_data(self, **kwargs):
context = super(ManageOperations, self).get_context_data(**kwargs)
context['operations_list'] = Operation.objects.filter(user=self.request.user).order_by('-date')
return context
def get_form_kwargs(self):
kwargs = super(ManageOperations, self).get_form_kwargs()
kwargs.update(user=self.request.user,
initial={'account': Account.objects.get(user=self.request.user, default=True)})
return kwargs
def form_valid(self, form):
operation = form.save(commit=False)
operation.currency = Account.objects.get(pk=form.instance.account_id).currency
self.update_account_balance(form)
form.instance.user = self.request.user
form.save()
return super(ManageOperations, self).form_valid(form)
I'd like to implement same modal windows both for "edit" and "delete" actions. I assume that it will be quite simple for OperationDelete view:
class OperationDelete(DeleteView, OperationMixIn):
model = Operation
success_url = reverse_lazy('manage_operations')
def delete(self, *args, **kwargs):
self.restore_account_balance(self.get_object().pk)
return super(OperationDelete, self).delete(*args, **kwargs)
I could just move delete method to my ManageOperations view and make it inherit from DeleteView.
Things are getting more complicated when it comes to editing existing Operation. Currently following code is responsible for handing an update of existing entry:
class OperationUpdate(UpdateView, OperationMixIn):
model = Operation
form_class = OperationForm
success_url = reverse_lazy('manage_operations')
def get_form_kwargs(self):
kwargs = super(OperationUpdate, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
def form_valid(self, form):
self.restore_account_balance(self.get_object().pk)
self.update_account_balance(form)
form.instance.user = self.request.user
return super(OperationUpdate, self).form_valid(form)
If I tired to merge it into ManageOperations view I would have to deal with multiple implementation of get_form_kwargs and form_valid methods.
Could you please tell me if I'm going in right direction with this or there is better and more elegant way to solve my problem? Creating one big ManageOperations view which would be responsible for all Operations releated actions seems a little bit silly to me.
I believe your approach is fine, except I would make the UpdateView and DeleteView AJAX views instead: let them return JSON instead of an HTML template and call them using AJAX from your template.
Keep ManageOperations as is
In your Edit modal form, let AJAX use the form data to post it to your OperationUpdate view.
Change your OperationUpdate view to return a JSON response. You could go all the way and use Django REST Framework for this, but it's quite easy to adapt existing Django generic CBVs to return JSON: Override render_to_response() (for the case the form is not valid) and form_valid() methods. You just return the bound form fields (values) and the errors in your JSON. If the form is valid you return the saved object in your JSON so the javascript can update the corresponding values in the table.
Change your OperationDelete view to also return a JSON response.
Add processing of the JSON responses in your Javascript, to display form errors, close the modal, update the values in the table, etc...
I've been working with Django for about 3 months now and feel I'm getting a bit better, working my way up to class based views. On the surface they seem cleaner and easier to understand and in some cases they are. In others, not so much. I am trying to use a simple drop down view via ModelChoiceField and a form. I can get it to work with a function based view as shown below in my views.py file:
def book_by_name(request):
form = BookByName(request.POST or None)
if request.method == 'POST':
if form.is_valid():
book_byname = form.cleaned_data['dropdown']
return HttpResponseRedirect(book_byname.get_absolute_url1())
return render(request,'library/book_list.html',{'form':form})
Here is my form in forms.py:
class BookByName(forms.Form):
dropdown = forms.ModelChoiceField(queryset=Book.objects.none())
def __init__(self, *args, **kwargs):
super(BookByName, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
self.fields['dropdown'].queryset = Book.objects.order_by('publisher')
This code works. When I have tried to convert to a Class Based View, that's when the trouble begins. I tried to do something like this in views.py:
class BookByNameView(FormView, View):
form_class = BookByName
initial = { 'Book' : Book }
template_name = 'library/book_list.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def get_success_url(self, *args):
return reverse_lazy('library:book_detail', args = (self.object.id,))
When using this with the same form, I receive an attribute error,
'BookByNameView' object has no attribute 'object'.
I've tried ListView as well and received several other errors along the way. The get_success_url also needs to take in a primary key and I can't figure out how to get that passed in as well. Again, I'm a 3 month Django newbie so please be gentle and thanks in advance for your thoughts and suggestions! I feel like I'm in the ballpark...just can't find my seat! I'm very open to doing this differently, if there's a cleaner/better way to do this!
Based on the latest feedback, it would appear the Class Based View should look like:
class BookNameView(FormView):
form_class = BookName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
Is this correct? I ran a test version of this and in response to your question as to why I am using self.object.id at all, I am trying to get the pk from the modelchoicefield that I am using to return the view I am trying to get. This may be where I am getting a bit lost. I am trying to get the detail view from the modelchoicefield dropdown, and return the book that is selected. However, I can't seem to pass the pk to this view successfully.
I updated my code to...
class BookByNameView(FormView, ListView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
But now it says error...Reverse for 'book_detail' with no arguments not found.
Why are you using self.object there at all? You used form.cleaned_data in the original view, that's what you should use in the class based version too. Note that the form is passed to form_valid.
Note that you've done lots of other weird things too. Your getmethod is pointless, as is your definition of the initial dict; you should delete them both. Also, FormView already inherits from View, there's no need to have View in your declaration explicitly.
You can override the form_valid() function in FormView to achieve what you want. If the form is valid then it is passed to the form_valid() function.
Try this:
class BookByNameView(FormView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def form_valid(self, form):
bookbyname = form.cleaned_data['dropdown']
return HttpResponseRedirect(bookbyname.get_absolute_url())
I'm running into a very strange issue where one form is initializing with the data from another form entirely. Here is the first view:
class UpdateProfileView(FormMixin, DetailView):
form_class = UpdateProfileForm
model = Profile
template_name = 'profile/update.html'
def get_context_data(self, **kwargs):
self.object = self.get_object()
context = super(UpdateProfileView, self).get_context_data(**kwargs)
...
self.initial['description'] = profile.about
context['form'] = self.get_form()
return context
...
This is the form that will return the correct data. As soon as it is loaded, however, the following form will return the initialized data from the previous one, even from different sessions, browsers, and locations:
class BountyUpdateForm(forms.ModelForm):
class Meta:
model = Bounty
fields = ("description", "banner")
class UpdateBountyView(UpdateView):
form_class = BountyUpdateForm
model = Bounty
template_name = 'bounty/update.html'
...
def get_context_data(self, **kwargs):
context = super(UpdateBountyView, self).get_context_data(**kwargs)
description = context['form']['description']
value = description.value()
# Value equals what was initialized by the previous form.
I'm really curious why these two forms are interacting in this way. Both form fields are called 'description', but that doesn't explain why the initial data from one would be crossing over to the other. Restarting the server seems to temporarily get the second form to show the correct values, but as soon as the first one is loaded, the second follows suit.
Any help would be greatly appreciated!
After some more searching, I was able to determine that my second view was having self.initial set to the same values as the first form by the time dispatch was being run. I couldn't determine why, but found these related questions:
Same problem, but no accepted answer:
Django(trunk) and class based generic views: one form's initial data appearing in another one's
Different problem, but good answer:
Setting initial formfield value from context data in Django class based view
My workaround was overriding get_initial() on my first form, instead of setting self.initial['description'] directly.
class UpdateProfileView(FormMixin, DetailView):
form_class = UpdateProfileForm
model = Profile
template_name = 'profile/update.html'
def get_initial(self):
return {
'description': self.object.about
}
def get_context_data(self, **kwargs):
...
# Removed the following line #
# self.initial['description'] = profile.about
...
context['form'] = self.get_form()
return context
Hope this helps anyone else who runs into this same problem. I wish I knew more about Django class-based views to be able to understand why this happens to begin with. However, I was unable to determine where self.initial was being set, beyond the empty dict in FormMixin().
views.py
from forms.py import PersonCreateForm
class PersonCreateView(CreateView):
model = Person
form_class = PersonCreateForm
template_name = "my_app/create_person.html"
def form_valid(self, form):
self.object = form.save()
return redirect('/homepage/')
class PeopleListView(ListView):
[...]
context.update({
'task_form': TaskCreateForm(),
return context
In my template I just add in action url which handle PersonCreateView.
<form action="{% url 'people_create' %}" method="post">{% csrf_token %}
When Form is valid then all data are saved without problems and it redirects me to '/homepage/.
But when my form is invalid then it redirects me to to {% url 'people_create' %} and shows errors at /homepage/people_create/
How can I avoid that? I want all errors show at same page without redirect.
Handle the form on the same view you build it, otherwise the page will change. You may mix django.views.generic.edit.ModelFormMixin into your PeopleListView so it has most of the features you need.
class PeopleListView(ModelFormMixin, ListView):
success_url = '/homepage/' # should use reverse() here
def get_context_data(self, **kwargs):
# only add the form if it is not already given to us
if not 'task_form' in kwargs:
kwargs['task_form'] = self.get_form()
return super(PeopleListView, self).get_context_data(**kwargs)
def post(self, request, *args, **kwargs):
# ListView won't have a post method, we define one
form = self.get_form()
if form.is_valid():
return self.form_valid(form) # default behavior will save and redirect
else:
return self.form_invalid(form) # default behavior has to be overridden (see below)
def form_invalid(self, form):
# Whatever you wanna do. This example simply reloads the list
self.object_list = self.get_queryset()
context = self.get_context_data(task_form=form)
return self.render_to_response(context)
There, you have three code paths:
Initial display will load the listview as usual, only an empty form will be added to the context.
On submitting valid input, the form_valid method is invoked, which will redirect to /homepage/.
On submitting invalid input, our overridden form_invalid method is invoked, which will render the page normally, except the form will contain the validation errors.
You may make the whole thing a bit more staightforward using a cached property for the form, but then you'd start working against Django's shipped views instead of with it, and might as well just use the basic View class and implement all logic by yourself. I'd stick with Django's views, but ymmv.