Django Updating a Value before a Save - django

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.

Related

How to update foreignkey field manually in UpdateView post method?

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

How to make an admin panel to review the entries per field in Django?

I am working on making an application in which users can submit entries and each submitted entry is reviewed by site admin.
Each entry has many fields and some fields can be accepted and some fields can be rejected.
For adding this functionality in admin, I added a custom view to
admin at '/review' url. Now in this view I want to create the
functionality to accept or reject a field, so that when the form is
submitted, I can take further actions based on that.
Till now i am able to render the modelform.
Now I want something like checkbox per field to mark a field as accepted or rejected, based on which I can take action in view. One way will be to create a form and then create BooleanField for each field manually.
How to accomplish this in DRY way ?
If I got you right, your particular question is about constructing a form with checkboxes for all the model's fields. Here's the way to do it:
class ReviewForm(forms.Form):
def __init__(self, model, *args, **kwargs):
super(ReviewForm, self).__init__(self, *args, **kwargs)
for field in model._meta.get_fields():
self.fields[field.name] = forms.BooleanField(label=field.name, initial=True)
def triage_view(request, *args, **kwargs):
form = ReviewForm(Entry, data=request.POST or None)
# do stuff with that form, for example
accepted_field_names = [key for key, val in form.cleaned_data if val]

super() save method on Django auth user's user change form

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.

Django admin change to_python output based on request

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)

Django Taggit - Tag Associations not Saving from Custom Admin Form

Going nuts over here...
From within the shell, I can do:
product.tags.add("a_new_tag")
The tag is added to the db, and the tag association with the product works correctly. (i.e., when I do Product.objects.filter(tags__name__in=["a_new_tag"] the appropriate product spits out)
What I need to do is add some tags in the admin when the form is processed.
Here is my form code (read the comments in lines 4 and 5):
class ProductForm(ModelForm):
def save(self, commit=True):
product = super(ProductForm, self).save(commit=False)
product.type="New Type to Confirm Info is being Saved Correctly" //this is saved to the product.
product.tags.add('a_new_tag_1') //the tag is saved to the taggit db, but the association with the product isn't kept.
product.save()
self.save_m2m()
return m
I tried to do the saving in the admin class instead, but this doesn't work either:
class ProductAdmin(admin.ModelAdmin):
form = ProductForm
def save_model(self, request, obj, form, change):
obj.type="new_type" //this works
obj.tags.add("a_new_tag_2") //tag association not saved
obj.save()
form.save_m2m()
What am I doing wrong? Thanks in advance!
So it turns out that form.save_m2m() was the culprit. If I took it out of my own code, and commented it out in django.contrib.admin.options.py (line 983), the associations as well as the tag were saved.
Obviously it's not a good idea to change django's code, so I ended up overriding change_view() in my ProductAdmin (as well as add_view()). I added the tags after calling super(), so form.save_m2m() wouldn't overwrite my tag associations.
This is strange because it goes directly against django-taggit's documentation which emphasizes how important it is to call form.save_m2m() : http://django-taggit.readthedocs.org/en/latest/forms.html
Well I dunno what's up, I'll probably go on the taggit google groups and notify 'em. In any case thanks David for your help, if nothing less pdb is AWESOME and I did not know about it before :)
Which tagging system are you using? Possibly you need to use product.tags.save()?