I'm doing some stuff on 'clean' on an admin ModelForm:
class MyAdminForm(forms.ModelForm):
def clean(self):
# Some stuff happens...
request.user.message_set.create(message="Some stuff happened")
class MyAdmin(admin.ModelAdmin):
form = MyAdminForm
Other than the threadlocals hack - how do I access request.user to set a message? I can't pass it to the form constructor because doesn't get called from my code.
You can't do it on the form without passing the user into the form constructor. Instead you can use the ModelAdmin.save_model function which is given the request object.
The save_model method is given the
HttpRequest, a model instance, a
ModelForm instance and a boolean value
based on whether it is adding or
changing the object. Here you can do
any pre- or post-save operations.
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model
Edit:
Since you want to put the logic/messages in the clean function you could do something like:
class MyAdminForm(forms.ModelForm):
user_messages = []
def clean(self):
# Some stuff happens...
user_messages.append("Some stuff happened")
class MyAdmin(admin.ModelAdmin):
form = MyAdminForm
def save_model(self, request, obj, form, change):
for message in form.user_messages:
request.user.message_set.create(message=message)
Very late edit:
user.message_set is set to be deprecated in Django 1.4. You should instead use ModelAdmin.message_user. https://docs.djangoproject.com/en/1.3/ref/contrib/admin/#django.contrib.admin.ModelAdmin.message_user
You would have to explicitly pass it there in constructor, which isn't a thing, that is usually done.
Are you sure you want to put that stuff into a form? What exactly you would like to do there? Isn't raising ValidationError enough?
Related
Suppose you have a ModelForm to which you have already binded the data from a request.POST. If there are fields of the ModelForm that I don't want the user to have to fill in and that are therefore not shown in the form (ex: the user is already logged in, I don't want the user to fill a 'author' field, I can get that from request.user), what is the 'Django' way of doing it ?
class RandomView(View):
...
def post(self, request, *args, **kwargs):
form = RandomForm(request.POST)
form.fill_remaining_form_fields(request) ### How would you implement this ???
if form.is_valid():
...
I have tried adding the fields to the form instance (ex: self.data['author'] = request.user) but given its a QueryDict it is immutable so it clearly isn't the correct way of doing this.
Any suggestions ?
My bad, the Django documentation actually explains how to do this in https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/#selecting-the-fields-to-use (see the Note).
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)
I want to override a ModelForm's save() function so that it updates a field on the model if the user pressed a particular submit button. I also want to check through some other fields and update their values, and I've done this on the ModelAdmin's save_model() function. However, the save_model() function is being passed None for the object. If I comment out the form's save() function, then the save_model() function works as expected.
Is there an issue with overriding both, or have I made a mistake somewhere?
Here's a minimal example:
admin.py:
class TestAdmin(admin.ModelAdmin):
form = TestForm
def save_model(self, request, obj, form, change):
print 'test'
super(PostAdmin, self).save_model(request, obj, form, change)
admin.site.register(TestModel, TestAdmin)
forms.py:
class TestForm(forms.ModelForm):
class Meta:
model = TestModel
def save(self, force_insert=False, force_update=False, commit=True):
print 'test'
super(TestForm, self).save(commit=True)
Your ModelForm needs to return the instance.
As far as I remember, just prior to save_model, the admin does a save(commit=False) and passes the unsaved instance to save_model. If you don't return anything, save() == None.
return super(CategoryForm, self).save(commit=True)
If you are overriding the ModelAdmin.save_model, and not calling the super().save_model (in your example you are, I can see.), you should explicitly call the form.save().
If you are calling the ModelForm.save somehow, through super or explicit calling, I don't see why it wouldn't work; but I can tell you that if I were to override the save_model, my preference would be to limit myself to overriding the Model.save().
I have a modified admin form, where I added a field that shall modify the values of the current model's parent object. Now, depending on the user, I need to
alter the queryset of that extra field
set another field as readonly (or better, even hide it completely)
Basically my code below works as I'd expect it. A superuser gets the whole queryset and the other field is not readonly. All other users get a limited queryset and the other field is readonly. However, once I open that site in a different browser and as a non-superuser, even the superuser get the same result as the non-superusers. Seems like django somehow caches the result? If I put some print statements inside the conditional branches though, they get printed correctly. So the method still gets called each time and seems to still perform these steps. Only with a wrong outcome.
Is that a caching problem? Am I doing something entirely wrong? Can it be a bug in the django test server?
def get_form(self, request, obj=None, **kwargs):
form = super(MultishopProductAdmin, self).get_form(request, obj, **kwargs)
if obj is not None:
form.declared_fields['categories'].initial = obj.product.category.all()
if not request.user.is_superuser:
user_site = request.user.get_profile().site
form.declared_fields['categories'].queryset = Category.objects.filter(site__id=user_site.id)
self.readonly_fields = ('virtual_sites', )
if obj is not None:
form.declared_fields['categories'].initial = obj.product.category.filter(site__id=user_site.id)
return form
Yes you are doing it wrong. In Django 1.2+ you can use get_readonly_fields.
From this answer:
The ModelAdmin is only instantiated once for all requests that it receives. So when you define the readonly fields like that, you're setting it across the board permanently.
Regarding altering the queryset. From the documentation:
class MyModelAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = super(MyModelAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(author=request.user)
To expand on dan-klasson's anwer: Do not ever set instance attributes in any ModelAdmin method (like self.readonly_fields) to prevent issues like the one you described. Only use Django ModelAdmin's options (which are class-level) or methods to manipulate any behavior. Regarding readonly fields, you can use the ModelAdmin.get_readonly_fields method which has this signature: get_readonly_fields(self, request, obj=None).
So I couldn't find a really clever way to do what I wanted with django admin's custom methods. What I ended up doing now is implementing the admin's change_view, setting up my own form manually and performing all my custom initializations from there.
I then provided a custom template by setting change_form_template, which is simply extending admin/change_form.html but rendering my own form instead of the default one. I also set extra_context['adminform'] = None so the default admin form gets removed.
That way I can now customize my form the way I need it to be but still use all the other admin conveniences. So far it seems to work very nicely. Not the very most elegant solution either I think, but the best I could think of.
I'm using the GenerickStackedInline which is a subclass of InlineModelAdmin which goes to ModelAdmin. When I override save_model method... it's not being called.
class LocatedItemStackedInline(generic.GenericStackedInline):
template = "admin/location_app/located_items/stacked.html"
model = LocatedItem
extra = 1
form = MyModelForm
raw_id_fields = ('location',)
def save_model(self, request, obj, form, change):
import ipdb;ipdb.set_trace()
super(LocatedItemStackedInline, self).save_model(request, obj, form, change)
def save_form(self, request, form, change):
import ipdb;ipdb.set_trace()
super(LocatedItemStackedInline, self).save_form(request, form, change)
So, I'm missing something?
Any clue?
Regards
The problem was that I was overriding the save_model method on the InlineAdmin instead of on the ModelAdmin itself.
Now is being called...
Cheers.
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model
describes the function you're talking about. My best guess is that you're confused about when and where that will be called. Also, are you sure you're actually working with the latest revision?
Edit: I'd guess that inline ModelAdmin objects may behave differently, given their otherwise special status.