Django get url parameter into form_valid - django

I have a url of the form:
path('makeoffer/<listing_id>', OfferCreateView.as_view(), name="CreateOfferview"),
This view is a form, with a couple of parameters. The listing_id needs to be saved as part of the Offer object, and is not user-editable. I essentially want to do this:
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.datetime = dt.now()
form.instance.listing_id = listing_id #(from the url)
How do I access the parameter from inside the form_valid function? I've tried get_context_data, but the only way I could see to do it would be for the get_context_data to pass it to the view, and then have the view pass it back to the form, which seems a bit hacky.

Related

How do you modify manytomanyfield in form in Django?

How to change the value of a many-to-many field in a Django form?
Preferably, I would like to change the value within the def form_valid of my Modelview. Here is a part of the form_valid in my view that I am having trouble with:
lesson = Lesson.objects.all().first()
for i in lesson.weekday.all():
form.instance.weekday.add(i)
form.instance.save()
Here, weekday is a many-to-many field. However, the form saves the "submitted" values of the weekday by the user, and not the changed one as shown in the above code. Interestingly, the below code works, although it is not a many-to-many field:
form.instance.name = lesson.name
form.instance.save()
I suspect that you are running the code before calling the super method. The code in the super method can look like this:
def form_valid(self, form):
self.object = form.save()
return super(ModelFormMixin, self).form_valid(form)
When form.save() runs clear all ManyToMany related values and sets the form values.
Probably you need run your code after calling the super method:
def form_valid(self, form):
# some code here...
return_value = super(MyView, self).form_valid(form)
lesson = Lesson.objects.all().first()
for i in lesson.weekday.all():
self.object.weekday.add(i)
# self.object.save() # Dont need call save here
return return_value

Django view with two forms not posting one

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?

Multiple django forms in modal windows for a single view

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...

Django Class Based View With ModelChoiceField

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())

Mutiple forms with Class Based View. How to show errors at the same page?

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.