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
Related
I am trying to make a simple form, that conditionally shows the website input field based on the value of another database field (that is not on the form) status. For the sake of this process the status field is not editable by the user, just by the admin. Both fields are in the same table: profile.
After working at this for a while I copped-out and just did the conditional hiding and showing on the template. But, I realise this is the unsophisticated method, and would like to improve it.
What I tried so far in forms.py:
class WebsiteForm(forms.ModelForm):
class Meta:
model = Profile
fields = (
'e-mail',
'website',
)
if Profile.status == 'personal' :
exclude = ('website',)
This method in forms.py works effectively, in that I can conditionally show and hide the field if I use test comparitors in the if statement like:
if 1 == 1:
or
if 1 != 1:
But, I cannot get an effective test using the field Profile.status, the value in the field seems to be unavailable at the point the if test in forms.py is performed.
If I use print(Profile.status) I get the following output in the terminal: user__profile__status__isnull, so I think this means that I am at least testing the correct field in the database. Although I am also noting that this output only shows at initialisation of runserver, not when the form page is accessed.
One final point, the user is authenticated and editing their own record.
Any help very much appreciated.
After a lot of trial and even more error, and some wide-ranging searching, I found the answer via the documentation at https://ccbv.co.uk/.
Essentially the path I decided to take was to use a different form for the respective fields that I wanted to use (I'm sure there are other solutions out there that add or subtract fields from the views). This involved changing the form_class with get_form_class:
# views.py
class telephone_view(UpdateView):
template_name = 'account/telephone.html'
#no need to define "form_class" here
#form_class = TelephoneForm
success_url = '/accounts/telephone/'
def get_form_class(self):
if self.request.user.profile.status == 'managed':
messages.success(self.request, _('you got the managed form'))
return TelephoneFormExtended
else:
messages.success(self.request, _('you got the other form'))
return TelephoneFormLight
def get_object(self, queryset=None):
return Profile.get_or_create_for_user(self.request.user)
def form_valid(self, form):
messages.success(self.request, _('Your telephone setting was updated'))
return super(telephone_view, self).form_valid(form)
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(telephone_view, self).dispatch(*args, **kwargs)
After working it out for myself I also found this answer which does the same thing:
Updateview with dynamic form_class
I am trying to manually change a foreign key field (Supplier) of a model (Expenditure). I override the UpdateView post method of Expenditure and handle forms for other models in this method too. A new SupplierForm is also rendered in this view and I am tracking if this form is changed via has_changed() method of the form. If this form has changed, what I ask is overriding the related_supplier field of ExpenditureForm and picking newly created Supplier by this statement:
if supplier_form_changed:
new_supplier = related_supplier_form.save(commit=False)
new_supplier.save()
....
# This statement seems to have no effect
self.object.related_supplier = new_supplier
I override the post method with super(), so even though I explicitly state save() method for all related forms, however I don't call the save method of main model (Expenditure) since it is already handled after super(). This is what start and end of my method looks like;
def post(self, request, *args, **kwargs):
context = request.POST
related_receipt_form = self.receipt_form_class(context, request.FILES)
related_supplier_form = self.supplier_form_class(context, request.FILES)
self.object = self.get_object()
related_receipt = self.object.receipt
related_supplier_form = self.supplier_form_class(context)
expenditure_form = self.form_class(context)
inlines = self.construct_inlines()
....
return super().post(self, request, *args, **kwargs)
You may find the full code of my entire view here:
https://paste.ubuntu.com/p/ZtCfMHSBZN/
So my problem is self.object.related_supplier = new_supplier statement does not have any effect. After the update, old related_supplier object is still there, new one is saved but not attached to the updated Expenditure. Strange thing is I am doing a similar thing in the same view (also in CreateView) with receipt and no problem whatsoever.
I debugged the code via PyCharm, before the execution of super(), I can confirm that self.object.related_supplier is the newly created one, but when the super() executed, it returns back to the original supplier object.
you can override the form valid method to add things manually, an example shown below
def form_valid(self, form):
related_supplier_form.instance.related_supplier = new_supplier
valid_data = super(UpdateView, self).form_valid(form)
return valid_data
I have successfuly created a complex form with a lot of fields using ModelForm and FormView. I managed to save those data and display it in the admin. Thus it is only the beginning of what I want to do.
My next goal is to use the inputs posted by the user via the form to perform calculations on it and then display the results of those calculations in another view.
What would be the best approach to do so? So far here are my files (I do not display all the fields since it is not relevant)
Here is the view I use to display the form
class SimulInputView(FormView):
form_class = SimulInputForm
template_name = 'apps/simulateur/formulaire/form.html'
success_url = reverse_lazy('simulateur_results')
def get_initial(self):
initial_data = super(SimulInputView, self).get_initial()
for key, value in dict_simul_form_default_data.items():
initial_data[key] = value
return initial_data
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
context['data'] = SimulateurData
return context
def form_valid(self, form):
form.instance.user = self.request.user
form.save()
return super().form_valid(form)
How should I modify my form_valid function to use another module which would perform calculations on the form data received before displaying them in another view?
EDIT
I modified my code as following. It seems to work well but I would like to know if it's good practice to handle it this way. Could you please share your opinion?
# VIEWS
class SimulInputView(FormView):
form_class = SimulInputForm
template_name = 'apps/simulateur/simulateur_form.html'
success_url = reverse_lazy('home')
def form_valid(self, form):
# save form and put in data_instance to get its id later
data_instance = form.save()
# save form data
form_data = form.data
# call calculs_simulation.py script which performs calculations on form data
result = calculs_simulation(form_data)
# put results of calculation in SimulResult model, and set the id for SimulInput foreignkey
result_model = SimulResult(simulinput_id=data_instance.id, **result)
# save the result model and get its id
result_model.save()
result_model_id = result_model.id
return redirect('simulateur_results', result_model_id)
class SimulResultView(DetailView):
model = SimulResult
template_name = 'apps/simulateur/simulateur_results.html'
# URLS
urlpatterns = [
path('formulaire/', simul_input_views.SimulInputView.as_view(), name="simulateur_form"),
path('resultats/<int:pk>/', simul_result_views.SimulResultView.as_view(), name="simulateur_results"),
]
You can return to another view with the i stance of the data saved to display the results you need. For example,
def form_valid(self, form):
.
.
data_instance = form.save()
return redirect(reverse('results_view', args=(data_instance.id, )))
Now in your results_view you can get your data_instance by id and calculate then render your results.
Hope this helps!
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...
So I'm in the process of working on a web application that has implemented security questions into it's registration process. Because of the way my models are setup and the fact that I am trying to use Django's Class based views (CBV), I've had a bit of problems getting this all to integrate cleanly. Here are what my models look like:
Model.py
class AcctSecurityQuestions(models.Model):
class Meta:
db_table = 'security_questions'
id = models.AutoField(primary_key=True)
question = models.CharField(max_length = 250, null=False)
def __unicode__(self):
return u'%s' % self.question
class AcctUser(AbstractBaseUser, PermissionsMixin):
...
user_questions = models.ManyToManyField(AcctSecurityQuestions, through='SecurityQuestionsInter')
...
class SecurityQuestionsInter(models.Model):
class Meta:
db_table = 'security_questions_inter'
acct_user = models.ForeignKey(AcctUser)
security_questions = models.ForeignKey(AcctSecurityQuestions, verbose_name="Security Question")
answer = models.CharField(max_length=128, null=False)
Here is what my current view looks like:
View.py
class AcctRegistration(CreateView):
template_name = 'registration/registration_form.html'
disallowed_url_name = 'registration_disallowed'
model = AcctUser
backend_path = 'registration.backends.default.DefaultBackend'
form_class = AcctRegistrationForm
success_url = 'registration_complete'
def form_valid(self, form):
context = self.get_context_data()
securityquestion_form = context['formset']
if securityquestion_form.is_valid():
self.object = form.save()
securityquestion_form.instance = self.object
securityquestion_form.save()
return HttpResponseRedirect(self.get_success_url())
else:
return self.render_to_response(self.get_context_data(form=form))
def get_context_data(self, **kwargs):
ctx = super(AcctRegistration, self).get_context_data(**kwargs)
if self.request.POST:
ctx['formset'] = SecurityQuestionsInLineFormSet(self.request.POST, instance=self.object)
ctx['formset'].full_clean()
else:
ctx['formset'] = SecurityQuestionsInLineFormSet(instance=self.object)
return ctx
And for giggles and completeness here is what my form looks like:
Forms.py
class AcctRegistrationForm(ModelForm):
password1 = CharField(widget=PasswordInput(attrs=attrs_dict, render_value=False),
label="Password")
password2 = CharField(widget=PasswordInput(attrs=attrs_dict, render_value=False),
label="Password (again)")
class Meta:
model = AcctUser
...
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise ValidationError(_("The two password fields didn't match."))
return self.cleaned_data
SecurityQuestionsInLineFormSet = inlineformset_factory(AcctUser,
SecurityQuestionsInter,
extra=2,
max_num=2,
can_delete=False
)
This post helped me a lot, however in the most recent comments of the chosen answer, its mentioned that formset data should be integrated into the form in the overidden get and post methods:
django class-based views with inline model-form or formset
If I am overiding the get and post how would I add in my data from my formset? And what would I call to loop over the formset data?
Inline formsets are handy when you already have the user object in the database. Then, when you initialize, it'll automatically preload the right security questions, etc. But for creation, a normal model formset is probably best, and one that doesn't include the field on the through table that ties back to the user. Then you can create the user and manually set the user field on the created through table.
Here's how I would do this using a just a model formset:
forms.py:
SecurityQuestionsFormSet = modelformset_factory(SecurityQuestionsInter,
fields=('security_questions', 'answer'),
extra=2,
max_num=2,
can_delete=False,
)
views.py:
class AcctRegistration(CreateView):
# class data like form name as usual
def form_valid(self):
# override the ModelFormMixin definition so you don't save twice
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, formset):
return self.render_to_response(self.get_context_data(form=form, formset=formset))
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = SecurityQuestionsFormSet(queryset=SecurityQuestionsInter.objects.none())
return self.render_to_response(self.get_context_data(form=form, formset=formset))
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = SecurityQuestionsFormSet(request.POST)
form_valid = form.is_valid()
formset_valid = formset.is_valid()
if form_valid and formset_valid:
self.object = form.save()
security_questions = formset.save(commit=False)
for security_question in security_questions:
security_question.acct_user = self.object
security_question.save()
formset.save_m2m()
return self.form_valid()
else:
return self.form_invalid(form, formset)
Regarding some questions in the comments about why this works the way it does:
I don't quite understand why we needed the queryset
The queryset defines the initial editable scope of objects for the formset. It's the set of instances to be bound to each form within the queryset, similar to the instance parameter of an individual form. Then, if the size of the queryset doesn't exceed the max_num parameter, it'll add extra unbound forms up to max_num or the specified number of extras. Specifying the empty queryset means we've said that we don't want to edit any of the model instances, we just want to create new data.
If you inspect the HTML of the unsubmitted form for the version that uses the default queryset, you'll see hidden inputs giving the IDs of the intermediary rows - plus you'll see the chosen question and answer displayed in the non-hidden inputs.
It's arguably confusing that forms default to being unbound (unless you specify an instance) while formsets default to being bound to the entire table (unless you specify otherwise). It certainly threw me off for a while, as the comments show. But formsets are inherently plural in ways that a single form aren't, so there's that.
Limiting the queryset is one of the things that inline formsets do.
or how the formset knew it was related until we set the acct_user for the formset. Why didn't we use the instance parameter
The formset actually never knows that it's related. Eventually the SecurityQuestionsInter objects do, once we set that model field.
Basically, the HTML form passes in the values of all its fields in the POST data - the two passwords, plus the IDs of two security question selections and the user's answers, plus maybe anything else that wasn't relevant to this question. Each of the Python objects we create (form and formset) can tell based on the field ids and the formset prefix (default values work fine here, with multiple formsets in one page it gets more complicated) which parts of the POST data are its responsibility. form handles the passwords but knows nothing about the security questions. formset handles the two security questions, but knows nothing about the passwords (or, by implication, the user). Internally, formset creates two forms, each of which handles one question/answer pair - again, they rely on numbering in the ids to tell what parts of the POST data they handle.
It's the view that ties the two together. None of the forms know about how they relate, but the view does.
Inline formsets have various special behavior for tracking such a relation, and after some more code review I think there is a way to use them here without needing to save the user before validating the security Q/A pairs - they do build an internal queryset that filters to the instance, but it doesn't look like they actually need to evaluate that queryset for validation. The main part that's throwing me off from just saying you can use them instead and just pass in an uncommitted user object (i.e. the return value of form.save(commit=False)) as the instance argument, or None if the user form is not valid is that I'm not 100% sure it would do the right thing in the second case. It might be worth testing if you find that approach clearer - set up your inline formset as you initially had it, initialize the formset in get with no arguments, then leave the final saving behavior in form_valid after all:
def form_valid(self, form, formset):
# commit the uncommitted version set in post
self.object.save()
form.save_m2m()
formset.save()
return HttpResponseRedirect(self.get_success_url())
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
self.object = form.save(commit=False)
# passing in None as the instance if the user form is not valid
formset = SecurityQuestionsInLineFormSet(request.POST, instance=self.object)
if form.is_valid() and formset.is_valid():
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)
If that works as desired when the form is not valid, I may have talked myself into that version being better. Behind the scenes it's just doing what the non-inline version does, but more of the processing is hidden. It also more closely parallels the implementation of the various generic mixins in the first place - although you could move the saving behavior into form_valid with the non-inline version too.