I am trying to edit django.contrib.auth.forms.UserChangeForm. Basically, auth_user's user edit page.
https://github.com/django/django/blob/master/django/contrib/auth/forms.py
According to source code, the form does not have a save() method, so it should inherit from forms.ModelForm right?
For full code, see here
class MyUserAdminForm(forms.ModelForm):
class Meta:
model = User
def __init__(self, *args, **kwargs):
super(MyUserAdminForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id: # username and user id
... the rest of the __init__ is setting readonly fields
.... some clean methods .....
def save(self, *args, **kwargs):
kwargs['commit'] = True
user = super(MyUserAdminForm, self).save(*args, **kwargs)
print user.username
print 'done'
return user
When I hit save, it said 'UserForm' object has no attribute 'save_m2m'. I've googled quite a bit, and tried to use add() but didn't work. What's causing this behaviour?
The thing is: the two print statements are printed. But the value never saved into database. I thought that the 2nd line would have saved once already.
Thanks
Remove the kwargs['commit'] = True line and see what happen.
Django Admin would invoke form.save_m2m(), which is hooked to the form when commit is False, here. The unconditional overriding of kwargs['commit'] = True would break the setattr of save_m2m() to form thus no attribute error is raised. The actual affected logic is here:
def save_form(self, request, form, change):
"""
Given a ModelForm return an unsaved instance. ``change`` is True if
the object is being changed, and False if it's being added.
"""
return form.save(commit=False)
You could find out that your version of form.save() overriding commit=False to commit=True unconditionally, thus Django Admin fails to continue as it believes form.save(commit=False) is invoked and thus form.save_m2m() needs to be called.
Refs the doc:
Another side effect of using commit=False is seen when your model has
a many-to-many relation with another model. If your model has a
many-to-many relation and you specify commit=False when you save a
form, Django cannot immediately save the form data for the
many-to-many relation. This is because it isn't possible to save
many-to-many data for an instance until the instance exists in the
database.
To work around this problem, every time you save a form using
commit=False, Django adds a save_m2m() method to your ModelForm
subclass. After you've manually saved the instance produced by the
form, you can invoke save_m2m() to save the many-to-many form data.
Related
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'm new in Django and i'm learning about the views and the methods and how they work, especially with this problem. The thing is that I would like to know how to automatically save a value of a field in my model after updating an object in a UpdateView, for example when I update an object, in this case a report where I can assign a person to do it, I would like to save a model value that shows the "status" and save the value of "assigned" or something like that, to know if the report was already assigned or not. I know there are methods and that maybe one of them could be done by overwriting the class, but I do not know how to apply it or which one to use.
For help this is a simple class of a UpdateViews that i'm using:
class reporteupdate(UpdateView):
model = reporte_fallo
form_class = ReporteAsignar
template_name = 'formulario/jefe_asignar.html'
success_url = reverse_lazy('formulario:reporte_listar_jefe')
and the field of the model that I would like to assign a value to is called status.
i'm waiting for your help, since I'm stuck with that doubt. Thanks!!!
the query dict will be changable after you create a copy of it in post method so you can do this:-
class SomeUpdateView(UpdateView):
model=your model
form_class=you form
def post(self, request, **kwargs):
request.POST = request.POST.copy()
request.POST['status'] = 'Assigned'
return super(SomeUpdateView, self).post(request, **kwargs)
You could perhaps set the status flag after the form has been successfully validated, by overriding the form_valid() method in your reporteupdate view:
class reporteupdate(UpdateView):
...
def form_valid(self, form):
# Call super() to save the model and return the success url
resp = super().form_valid(form)
# Set your status flag
self.object.status = 'assigned'
self.object.save()
return resp
I'm trying to update a counter in a model to save database queries. So I have an article model with a picture_count field. Pictures are m2m with the article.
When every I add or remove a picture from the article (using Django Admin) I want to update the article picture_count. But it seems that I'm going about it wrong.
I thought I could simply override the save method of my article model. But this doesn't work as the
def save(self, *args, **kwargs):
self.picCount = self.pictures.count()
super(Articles, self).save(*args, **kwargs) # Call the "real" save() method.
The problem is that the m2m haven't been updated yet. I have tried calling it after (then calling save again) but the object is outdated. Should I refresh the object and save it again or is there a better place to update this count?
Since you're handling m2m relations, you want to override the save_related method.
Admin.py
def save_related(self, request, form, formsets, change):
self.picCount = self.pictures.count()
super(Articles, self).save_related(request, form, formsets, change)
Note that if you're using python 3.x you don't have to pass anything to super call.
I've created a form which is a forms.ModelForm. On the "view" side, I've created a view which is a generic.UpdateView.
In those 2 differents classes, I have is_valid() on one side, and form_valid() on the other side.
class ProfileForm(FormForceLocalizedDateFields):
class Meta:
model = Personne
fields = ('sexe', 'statut', 'est_fumeur',
'est_physique', 'date_naissance')
exclude = ('user', 'est_physique')
# blabla fields declaration
def is_valid(self):
pass
and edit view:
class EditView(LoginRequiredMixin, generic.UpdateView):
model = Personne
template_name = 'my_home/profile/edit.html'
form_class = ProfileForm
success_url = reverse_lazy('my_home_index')
# blabla get_initial() and get_object() and get_context_data()
def form_valid(self, form):
# username = form.cleaned_data['username']
# Hack: redirect on same URL:
# - if user refreshes, no form re-send
# - if user goes back, no form re-send too, classical refresh
site_web = u"{0}://{1}".format(
self.request.scheme, self.request.META['HTTP_HOST']
)
return HttpResponseRedirect(u'{0}{1}'.format(
site_web, self.request.META['PATH_INFO']
))
My form shows 3 fields of 3 different models :
User,
Person which has a foreign key to User
Picture which has a foreign key to Person
Where should I create the code that update those fields, and why?
generic.UpdateView is supposed to help us when updating fields, but it seems that when you have fields not belonging to the model you edit, you have to write all the "update" by hand.
is_valid on the surface just tells you whether or not the form is valid, and thats the only job it should ever do..
From the source code:
def is_valid(self):
"""
Returns True if the form has no errors. Otherwise, False. If errors are
being ignored, returns False.
"""
return self.is_bound and not self.errors
Underneath this, what it also does is (from docs)
run validation and return a boolean designating whether the data was valid:
The validation is ran because errors is a property that will call full_clean if the validation hasn't been called yet.
#property
def errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors
Where should I create the code that update those fields, and why?
In the form_valid method because by this point you've found out that your validation has verified that it is safe to update your model.
I'm wondering how to change the behavior of a form field based on data in the request... especially as it relates to the Django Admin. For example, I'd like to decrypt a field in the admin based on request data (such as POST or session variables).
My thoughts are to start looking at overriding the change_view method in django/contrib/admin/options.py, since that has access to the request. However, I'm not sure how to affect how the field value displays the field depending on some value in the request. If the request has the correct value, the field value would be displayed; otherwise, the field value would return something like "NA".
My thought is that if I could somehow get that request value into the to_python() method, I could directly impact how the field is displayed. Should I try passing the request value into the form init and then somehow into the field init? Any suggestions how I might approach this?
Thanks for reading.
In models.py
class MyModel(models.Model):
hidden_data = models.CharField()
In admin.py
class MyModelAdmin(models.ModelAdmin):
class Meta:
model = MyModel
def change_view(self, request, object_id, extra_context=None):
.... # Perhaps this is where I'd do a lot of overriding?
....
return self.render_change_form(request, context, change=True, obj=obj)
I haven't tested this, but you could just overwrite the render_change_form method of the ModelAdmin to sneak in your code to change the field value between when the change_view is processed and the actual template rendered
class MyModelAdmin(admin.ModelAdmin):
...
def render_change_form(self, request, context, **kwargs):
# Here we have access to the request, the object being displayed and the context which contains the form
form = content['adminform'].form
field = form.fields['field_name']
...
if 'obj' in kwargs:
# Existing obj is being saved
else:
# New object is being created (an empty form)
return super(MyModelAdmin).render_change_form(request, context, **kwargs)