Django: customizing the message after a successful form save - django

whenever I save a model in my Admin interface, it displays the usual "successfully saved message."
However, I want to know if it's possible to customize this message because I have a situation where I want to warn the user about what he just saved and the implications of these actions.
class PlanInlineFormset(forms.models.BaseInlineFormset):
def clean(self):
### How can I detect the changes?
### (self.changed_data doesn't work because it's an inline)
### and display what he/she just changed at the top AFTER the successful save?
class PlanInline(admin.TabularInline):
model = Plan
formset = PlanInlineFormset

Django (> version 1.2) uses the messages framework for admin messages. You can add additional messages using that interface. Here's an example:
from django.contrib import messages
class SomeModelAdmin(admin.ModelAdmin):
# your normal ModelAdmin stuff goes here
def save_model(self, request, obj, form, change):
# add an additional message
messages.info(request, "Extra message here.")
super(SomeModelAdmin, self).save_model(request, obj, form, change)
To detect changes to the object being saved, you should be to override the save_model method of ModelAdmin, and compare the object the method is passed to the version currently in the database. To do this in the case of inlines, you can override the save_formset method. A possible approach might look like (untested code):
class SomeModelAdmin(admin.ModelAdmin):
# your normal ModelAdmin stuff goes here
def save_formset(self, request, form, formset, change):
if not change:
formset.save()
else:
instances = formset.save(commit=False)
for instance in instances:
try:
# if you've got multiple types of inlines
# make sure your fetching from the
# appropriate model type here
old_object = SomeOtherModel.get(id=instance.id)
except SomeOtherModel.DoesNotExist:
continue
if instance.field_x != old_object.field_x:
messages.info(request, "Something Changed")
instance.save()
formset.save_m2m()

If you're using Django 1.2 or newer, the messages framework may hold the answer.
http://docs.djangoproject.com/en/dev/ref/contrib/messages/

Related

Django admin save() doesn't get data from ManyToManyField

I'm trying to get chosen objects from affiliate_networks in Django admin when the user clicks the submit button.
When I choose one from affiliate_networks and submit it, the console prints an empty of affiliate_networks, and then I come back to the page and the chosen object is stored properly. So, I submit it again, then the console prints the chosen object. save() only receives objects that are already stored, not objects that I choose before saving.
Is there a way, that I can have save() to notice affiliate_networks to have any object chosen?
class Store(models.Model):
...
affiliate_networks = models.ManyToManyField(AffiliateNetwork, blank=True)
def save(self, *args, **kwargs):
print(self.affiliate_networks.all())
You can't do it in save() - as you have discovered, Django admin doesn't save ManyToMany objects until afterwards. You need to do it in the save_related method of ModelAdmin. See https://timonweb.com/posts/many-to-many-field-save-method-and-the-django-admin/
In admin.py:
...
class StoreAdmin(admin.ModelAdmin):
def save_related(self, request, form, formsets, change):
super(StoreAdmin, self).save_related(request, form, formsets, change)
print(form.instance.affiliate_networks.all())
...
admin.site.register(Store, StoreAdmin)

Readonly View for 10 Django Class Based Views

I have 10 Django Class Based Views and I want to display them read-only to the user.
I want the whole form to be read-only, not only some values. Submitting the form should be disabled on the client (HTML) and a second time on the server (POST not allowed).
Is there a MixIn or an other simple solution?
Here's a mixin that does two simple things:
Sets html attributes for all fields in form for disabled andreadonly.
Overrides the form_valid method of your CBV so that no model saving ever happens; instead, the template is rendered (just as if there was no submitted data). The user, this way, does not cause any action if they submitted the form.
Form field errors may appear next to disabled fields if you are rendering the full form in your template; solve this by either erasing the form's error dictionary or by rendering each field individually without errors.
from django.views.generic.edit import FormMixin, ModelFormMixin
class ReadOnlyModelFormMixin(ModelFormMixin):
def get_form(self, form_class=None):
form = super(ReadOnlyModelFormMixin, self).get_form()
for field in form.fields:
# Set html attributes as needed for all fields
form.fields[field].widget.attrs['readonly'] = 'readonly'
form.fields[field].widget.attrs['disabled'] = 'disabled'
return form
def form_valid(self, form):
"""
Called when form is submitted and form.is_valid()
"""
return self.form_invalid(form)
Extending this concept for a non-model FormView is pretty simple; inherit from class FormMixin instead. :)
To disallow POST requests in general for class-based views you could use the following mixin:
class DisallowPostMixin(object):
def post(self, request, *args, **kwargs):
return self.http_method_not_allowed(self, request, *args, **kwargs)
If you also want to disable certain form fields etc. you could add the get_form method from Ian Price's answer.
You can hack it through middleware. On request - check view name and request method (if post - redirect), on response - add input attrs in response.content. But mixin - best solution.

Conditionally override the model Django admin successfully saved message

I am overriding the save method on a Django model. There are some cases where I do not save the model. In these cases, I can't seem to be able to figure out how to conditionally override the "The citation 1111 was added successfully." message that is shown after returning back to the admin list interface (as opposed to the entry form interface).
I don't think you can override that just by overriding save Django's admin interface uses model forms and the messages framework.
I think something like this happens, it's more complicated than this but more or less:
models.py
class MyModel(models.Model):
foo = models.CharField(...)
bar = models.CharField(...)
def save(self, *args, **kwargs):
if self.foo == self.bar: # We only save if foo == bar
super(MyModel, self).save(*args, **kwargs)
forms.py (Django admin uses model forms, so this is an example)
class MyModelForm(ModelForm):
class Meta:
model = MyModel
views.py
def save(request):
if request.method == 'POST':
form = MyModelForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, 'MyModel was saved!.')
Now regardless of what form.save() actually did the message is still sent out anyway, we have no way of knowing if you saved or not in your overridden save method and this is probably whats happening in the django admin system.
An alternative would be to create a custom model form for the admin to use for this model and define a custom validation method, so the form doesn't validates unless foo == bar. Or you could override the save method on the form, you'll need to have a look around the django admin code, it probably is possible, just a bit trixy :p

Display custom message from signal in the admin

I have a pre-save signal listener that updates a second model. The same as this example:
Django Signals to update a different model
I'd like to let the user know that the listener succeeded in updating the model and provide some information. Normally, I would think I could use the built in messages functionality that django has. The problem is that the signal doesn't have access to 'request'. So I can't see how to use the built in Django Messages Framework.
https://docs.djangoproject.com/en/dev/ref/contrib/messages/
Is there a known method for sending a message to the user in the admin? Maybe by overriding the save() method for one of the models? (the one sending the signal, or receiving), but I don't think the save() method has access to 'request' either?
This must be something others want to do as well?
You can override save_model method in ModelAdmin. Something like this:
from django.contrib import messages
# your imports
...
# your code
def save_model(self, request, obj, form, change):
obj.user = request.user
obj.save()
# you can just call super(YourModelAdminName, self).save_model(request, obj, form, change)
messages.add_message(request, messages.INFO, 'Text of message')

accessing request object within a django admin inline model

I have the following model / form / admin hierarchy. What I want to achieve is basically, add the currently logged in user to a field of the AttachmentInlines. So I thought I would need the request object which gets passed in at several methods, implemented below.
However, none of these methods gets called in that context, obviously because I am working with Inline models.
I COULD work in InvoiceAdmin#save_model, but that sucks because I would then have to that in a whole bunch of model admins all using the same Inlines. Which doesn't seem too DRY to me.
So is there any other way to get access to the request object from within my Inline models in the admin?
class AttachmentForm(forms.ModelForm):
class Meta:
model = Attachment
def save(self, request, obj, *args, **kwargs):
print 'I never get called :-('
class AttachmentInlines(generic.GenericStackedInline):
model = Attachment
form = AttachmentForm
def save_model(self, request, obj, form, change):
print 'I never get called either...'
class InvoiceAdmin(admin.ModelAdmin):
inlines = [ AttachmentInlines, ]
def save_model(self, request, obj, form, change):
print 'I DO get called, but I am not needed...'