accessing request object within a django admin inline model - django

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

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)

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

Django GenericTabularInline for multiple databases

I've been trying to make the GenericTabularInline class work in a two-admin two-databases setup by inheriting from it and overriding some methods in the BaseModelAdmin class, as is done in the Django docs (https://docs.djangoproject.com/en/dev/topics/db/multi-db/), but if a child model is edited in the inline form, it always writes to the default database (I want the second admin to deal exclusively with a secondary database, models are the same for both), so I must not be overriding some method(s) or doing something wrong. Here's the class I have so far:
class MultiDBGenericTabularInline(generic.GenericTabularInline):
using = settings.SECONDARY_DATABASE
def save_model(self, request, obj, form, change):
# Tell Django to save objects to the 'other' database.
obj.save(using=self.using)
def delete_model(self, request, obj):
# Tell Django to delete objects from the 'other' database
obj.delete(using=self.using)
def queryset(self, request):
# Tell Django to look for objects on the 'other' database.
return super(MultiDBGenericTabularInline, self).queryset(request).using(self.using)
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
# Tell Django to populate ForeignKey widgets using a query
# on the 'other' database.
return super(MultiDBGenericTabularInline, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
# Tell Django to populate ManyToMany widgets using a query
# on the 'other' database.
return super(MultiDBGenericTabularInline, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)
#Override these three methods; otherwise the log manager attempts
#to write to the main db and raises an exception.
def log_addition(self, request, object):
pass
def log_change(self, request, object, message):
pass
def log_deletion(self, request, object, object_repr):
pass
Any help or hints are appreciated.
I realize this is an old question, but I've stumbled across a very similar thing recently. The trick is to override the parent Model Admin's save_formset method. In my case, the solution is to do something like this:
class SomeTabularInline(admin.TabularInline):
# stuff
class MyModelAdmin(admin.ModelAdmin):
using = 'something'
inlines = (SomeTabularInline,)
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for obj in formset.deleted_objects:
obj.delete(using=self.using)
for instance in instances:
instance.save(using=self.using)
formset.save_m2m()
Note: I'm using a TabularInline instance, and not a GenericTabularInline but they both descend from InlineModelAdmin; so I'm hopeful that this would work in your case.
Source: https://docs.djangoproject.com/en/1.9/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_formset

Django: customizing the message after a successful form save

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/

How to clean form data depending on logged-in user in django admin panel?

I've looked at several questions here that looked similar, but none of them discussed the problem from the perspective of admin panel.
I need to check if user has permission to leave a field empty. I wanted to use request.user but I don't knot how to pass request from EntryAdmin to ModelForm. I wanted to do something like this:
class EntryAdminForm(ModelForm):
class Meta:
model = Entry
def clean_category(self):
if not self.request.user.has_perm('blog.can_leave_empty_category') and not bool(self.category):
raise ValidationError(u'You need to choose a Category!')
else:
return self.cleaned_data['category']
You could override the ModelAdmin.get_form, by adding the request as an attribute of the newly created form class (should be thread-safe).
Something along these lines:
class EntryAdmin(admin.ModelAdmin):
form = EntryAdminForm
def get_form(self, request, *args, **kwargs):
form = super(EntryAdmin, self).get_form(request, *args, **kwargs)
form.request = request
return form
Then the code in your question should work.