I have 2 models related by M2M type of relationship. I use filter_horizontal in the admin for editing my entities.
However, I would like to have a control on what is presented in the left side of the filter_horizontal widget. For example, I would like to filter and show only those entities that match some certain criteria.
I think I found it!
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "cars":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
ModelAdmin.formfield_for_manytomany(db_field, request, **kwargs)
This subject is always tricky in the Django admin. I suppose that in the inline defenition you can do something like this:
class BAdmin(admin.TabularInline):
...
def get_queryset(self, request):
qs = super(BAdmin, self).get_queryset(request)
return qs.filter(user=request.user)
https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_queryset
Related
I have relatively simple data model with User, Group and Task. Each group has its own tasks and users. Users can be only assigned to one group.
Tasks belong to groups and each task has manyToMany field for users, so multiple users can have the same task assigned.
In my admin when assigning users to task it shows all created users, I want it to only show users from the same group as the task.
What would be the best approach?
I have checked available customization options for admin.ModelAdmin but haven't found anything related to my problem.
you can use formfield_for_manytomany
the formfield_for_manytomany method can be overridden to change the default formfield for a many to many field
in your case change your admin.py to :
class TaskAdmin(admin.ModelAdmin):
def get_object(self, request, object_id, s):
# Hook obj for use in formfield_for_manytomany
self.obj = super(TaskAdmin, self).get_object(request, object_id)
# print ("Got object:", self.obj)
return self.obj
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "user":
kwargs["queryset"] = User.objects.filter(task=self.obj)
return super().formfield_for_manytomany(db_field, request, **kwargs)
admin.site.register(Task, TaskAdmin)
You can customize the model admin using a method: formfield_for_manytomany
class TaskAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "users":
# Filter your users below as per your condition
kwargs["queryset"] = Users.objects.filter()
return super(TaskAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
The problem is I have a model called Gift. And it has a boolean field 'giftbought' that I want to hide in admin interface when the object is being created and show it when it is being updated.
I tryed making a form, overriding init method like:
class GiftForm(forms.ModelForm):
giftbought = forms.BooleanField(label=u"Bought?", required=False)
class Meta:
model = Gift
def __init__(self, *args, **kwargs):
super(GiftForm, self).__init__(*args, **kwargs)
if not self.instance.pk:
del self.fields['giftbought']
But it doesn't work for admin, like it is being said in:
Remove fields from ModelForm
I thing I needed to make a class ModelAdmin, overriding get_form method, but I don't know how to check if I is_instance or not...
It would be something like:
class GiftAdmin(admin.ModelAdmin):
model = Gift
def get_form(self, request, obj=None, **kwargs):
# that IF doesnt work!!!
if not self.instance.pk:
self.exclude = ('giftbought',)
return super(GiftAdmin, self).get_form(request, obj=None, **kwargs)
admin.site.register(Gift, GiftAdmin)
Any hint?
Definitely your best bet is ModelAdmin. I think you got it right except for the if test.
You should be able to do it like this:
class GiftAdmin(admin.ModelAdmin):
model = Gift
def get_form(self, request, obj=None, **kwargs):
self.exclude = []
# self.instance.pk should be None if the object hasn't yet been persisted
if obj is None:
self.exclude.append('giftbought')
return super(GiftAdmin, self).get_form(request, obj, **kwargs)
admin.site.register(Gift, GiftAdmin)
Notice there are minor changes to the method code. You should check the docs, I'm sure you'll find everything you need there.
Hope this helps!
I'm having some trouble overriding the queryset for my inline admin.
Here's a bog-standard parent admin and inline admin:
class MyInlineAdmin(admin.TabularInline):
model = MyInlineModel
def queryset(self, request):
qs = super(MyInlineAdmin, self).queryset(request)
return qs
class ParentAdmin(admin.ModelAdmin):
inlines = [MyInlineAdmin]
admin.site.register(ParentAdminModel, ParentAdmin)
Now I can do qs.filter(user=request.user) or qs.filter(date__gte=datetime.today()) no problem.
But what I need is either the MyInlineModel instance or the ParentAdminModel instance (not the model!), as I need to filter my queryset based on that.
Is it possible to get something like self.instance or obj (like in get_readonly_fields() or get_formset()) inside the queryset() method?
Hope this makes sense. Any help is much appreciated.
class MyInlineAdmin(admin.TabularInline):
model = MyInlineModel
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
"""enable ordering drop-down alphabetically"""
if db_field.name == 'car':
kwargs['queryset'] = Car.objects.order_by("name")
return super(MyInlineAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
class ParentAdmin(admin.ModelAdmin):
inlines = [MyInlineAdmin]
admin.site.register(ParentAdminModel, ParentAdmin)
Im assuming your models look something like:
class MyInlineModel(models.Model):
car=models.Foreignkey(Car)
#blah
for more on this; read the django Docs on formfield_for_foreignkey-->
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
In Django's ModelAdmin, I need to display forms customized according to the permissions a user has. Is there a way of getting the current user object into the form class, so that i can customize the form in its __init__ method?
I think saving the current request in a thread local would be a possibility but this would be my last resort because I'm thinking it is a bad design approach.
Here is what i did recently for a Blog:
class BlogPostAdmin(admin.ModelAdmin):
form = BlogPostForm
def get_form(self, request, **kwargs):
form = super(BlogPostAdmin, self).get_form(request, **kwargs)
form.current_user = request.user
return form
I can now access the current user in my forms.ModelForm by accessing self.current_user
EDIT: This is an old answer, and looking at it recently I realized the get_form method should be amended to be:
def get_form(self, request, *args, **kwargs):
form = super(BlogPostAdmin, self).get_form(request, *args, **kwargs)
form.current_user = request.user
return form
(Note the addition of *args)
Joshmaker's answer doesn't work for me on Django 1.7. Here is what I had to do for Django 1.7:
class BlogPostAdmin(admin.ModelAdmin):
form = BlogPostForm
def get_form(self, request, obj=None, **kwargs):
form = super(BlogPostAdmin, self).get_form(request, obj, **kwargs)
form.current_user = request.user
return form
For more details on this method, please see this relevant Django documentation
This use case is documented at ModelAdmin.get_form
[...] if you wanted to offer additional fields to superusers, you could swap in a different base form like so:
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if request.user.is_superuser:
kwargs['form'] = MySuperuserForm
return super().get_form(request, obj, **kwargs)
If you just need to save a field, then you could just override ModelAdmin.save_model
from django.contrib import admin
class ArticleAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.user = request.user
super().save_model(request, obj, form, change)
I think I found a solution that works for me: To create a ModelForm Django uses the admin's formfield_for_db_field-method as a callback.
So I have overwritten this method in my admin and pass the current user object as an attribute with every field (which is probably not the most efficient but appears cleaner to me than using threadlocals:
def formfield_for_dbfield(self, db_field, **kwargs):
field = super(MyAdmin, self).formfield_for_dbfield(db_field, **kwargs)
field.user = kwargs.get('request', None).user
return field
Now I can access the current user object in the forms __init__ with something like:
current_user=self.fields['fieldname'].user
stumbled upon same thing and this was first google result on my page.Dint helped, bit more googling and worked!!
Here is how it works for me (django 1.7+) :
class SomeAdmin(admin.ModelAdmin):
# This is important to have because this provides the
# "request" object to "clean" method
def get_form(self, request, obj=None, **kwargs):
form = super(SomeAdmin, self).get_form(request, obj=obj, **kwargs)
form.request = request
return form
class SomeAdminForm(forms.ModelForm):
class Meta(object):
model = SomeModel
fields = ["A", "B"]
def clean(self):
cleaned_data = super(SomeAdminForm, self).clean()
logged_in_email = self.request.user.email #voila
if logged_in_email in ['abc#abc.com']:
raise ValidationError("Please behave, you are not authorised.....Thank you!!")
return cleaned_data
Another way you can solve this issue is by using Django currying which is a bit cleaner than just attaching the request object to the form model.
from django.utils.functional import curry
class BlogPostAdmin(admin.ModelAdmin):
form = BlogPostForm
def get_form(self, request, **kwargs):
form = super(BlogPostAdmin, self).get_form(request, **kwargs)
return curry(form, current_user=request.user)
This has the added benefit making your init method on your form a bit more clear as others will understand that it's being passed as a kwarg and not just randomly attached attribute to the class object before initialization.
class BlogPostForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.current_user = kwargs.pop('current_user')
super(BlogPostForm, self).__init__(*args, **kwargs)
I'm trying to initialize the form attribute for MyModelAdmin class inside an instance method, as follows:
class MyModelAdmin(admin.ModelAdmin):
def queryset(self, request):
MyModelAdmin.form = MyModelForm(request.user)
My goal is to customize the editing form of MyModelForm based on the current session. When I try this however, I keep getting an error (shown below). Is this the proper place to pass session data to ModelForm? If so, then what may be causing this error?
TypeError at ...
Exception Type: TypeError
Exception Value: issubclass() arg 1 must be a class
Exception Location: /usr/lib/pymodules/python2.6/django/forms/models.py in new, line 185
Combining the good ideas in Izz ad-Din Ruhulessin's answer and the suggestion by Cikić Nenad, I ended up with a very awesome AND concise solution below:
class CustomModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
self.form.request = request #so we can filter based on logged in user for example
return super(CustomModelAdmin, self).get_form(request,**kwargs)
Then just set a custom form for the modeladmin like:
form = CustomAdminForm
And in the custom modelform class access request like:
self.request #do something with the request affiliated with the form
Theoretically, you can override the ModelAdmin's get_form method:
# In django.contrib.admin.options.py
def get_form(self, request, obj=None, **kwargs):
"""
Returns a Form class for use in the admin add view. This is used by
add_view and change_view.
"""
if self.declared_fieldsets:
fields = flatten_fieldsets(self.declared_fieldsets)
else:
fields = None
if self.exclude is None:
exclude = []
else:
exclude = list(self.exclude)
exclude.extend(kwargs.get("exclude", []))
exclude.extend(self.get_readonly_fields(request, obj))
# if exclude is an empty list we pass None to be consistant with the
# default on modelform_factory
exclude = exclude or None
defaults = {
"form": self.form,
"fields": fields,
"exclude": exclude,
"formfield_callback": curry(self.formfield_for_dbfield, request=request),
}
defaults.update(kwargs)
return modelform_factory(self.model, **defaults)
Note that this returns a form class and not a form instance.
If some newbie, as myself, passes here:
I had to define:
class XForm(forms.ModelForm):
request=None
then at the end of the previous post
mfc=modelform_factory(self.model, **defaults)
self.form.request=request #THE IMPORTANT statement
return mfc
i use queryset fot filtering records, maybe this example help you:
.....
.....
def queryset(self, request):
cuser = User.objects.get(username=request.user)
qs = self.model._default_manager.get_query_set()
ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
if ordering:
qs = qs.order_by(*ordering)
qs = qs.filter(creator=cuser.id)
return qs
Here is a production/thread-safe variation from nemesisfixx solution:
def get_form(self, request, obj=None, **kwargs):
class NewForm(self.form):
request = request
return super(UserAdmin, self).get_form(request, form=NewForm, **kwargs)
class CustomModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
get_form = super(CustomModelAdmin, self).get_form(request,**kwargs)
get_form.form.request = request
return get_form
Now in ModelForm, we can access it by
self.request
Example:
class CustomModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TollConfigInlineForm, self).__init__(*args, **kwargs)
request = self.request
user = request.user