Dynamically setting readonly_fields in django admin - python

Can I change readonly_fields in my TranslationAdmin class dependent on the value of a certain field in the Translation being viewed? If so, how would I do that?
The only thing I've come up with is to make a widget that looks at the Translation and decides whether to be a readonly widget or not, but that seems like overkill for this.

You can inherit the function get_readonly_fields() in your admin and set readonly fields according your model's certain field value
class TranslationAdmin(admin.ModelAdmin):
...
def get_readonly_fields(self, request, obj=None):
if obj.certainfield == something:
return ('field1', 'field2')
else:
return super(TranslationAdmin, self).get_readonly_fields(request, obj)
I hope it will help you.

Here is an example of:
Creating a ModelAdmin (GISDataFileAdmin) with readonly_fields
Inheriting from that ModelAdmin (ShapefileSetAdmin) and adding an additional value to readonly_fields
class GISDataFileAdmin(admin.ModelAdmin):
# Note(!): this is a list, NOT a tuple
readonly_fields = ['modified', 'created', 'md5',]
class ShapefileSetAdmin(GISDataFileAdmin):
def get_readonly_fields(self, request, obj=None):
# inherits readonly_fields from GISDataFileAdmin and adds another
# retrieve current readonly fields
ro_fields = super(ShapefileSetAdmin, self).get_readonly_fields(request, obj)
# check if new field already exists, if not, add it
#
# Note: removing the 'if not' check will add the new read-only field
# each time you go to the 'add' page in the admin
# e.g., you can end up with:
# ['modified', 'created', 'md5', 'shapefile_load_path', 'shapefile_load_path, 'shapefile_load_path', etc.]
#
if not 'shapefile_load_path' in ro_fields:
ro_fields.append('shapefile_load_path')
return ro_fields

Related

Django admin inline - show only when changing object

I have a UserProfile which has many required (not null but blank) fields and I'm trying not to show it when adding a user.
I tried multiple things, for example:
def get_formsets_with_inlines(self, request, obj=None):
if not obj:
return []
return super().get_formsets_with_inlines(request, obj)
But after saving User, django raises error which says that fields from UserProfile can't be null.
Do you know how to make it work?
As of Django 3.0, there is a ModelAdmin.get_inlines() method, which you can override like this:
def get_inlines(self, request, obj=None):
if obj:
return [FirstInline, SecondInline]
else:
return []
See Django Documentation.
ModelAdmin provides a method get_inline_instances for conditional inlines.
from the docs:
The get_inline_instances method is given the HttpRequest and the obj
being edited (or None on an add form) and is expected to return a list
or tuple of InlineModelAdmin objects.
for example:
class MyModelAdmin(admin.ModelAdmin):
inlines = (MyInline,)
def get_inline_instances(self, request, obj=None):
return [inline(self.model, self.admin_site) for inline in self.inlines]
here you can check if obj is present or not.
One important point from the docs:
If you override this method, make sure that the returned inlines are
instances of the classes defined in inlines or you might encounter a
“Bad Request” error when adding related objects.
I use "get_inlines()" added to Django since v3.0 instead of "inlines = ()" as shown below so that "AddressInline" is not displayed when adding a user but it's displayed when changing a user:
# "admin.py"
class AddressInline(admin.TabularInline):
model = Address
#admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
# inlines = (AddressInline,)
# Here
def get_inlines(self, request, obj=None):
if obj:
return (AddressInline,)
else:
return ()
fieldsets = (
# ...
)
add_fieldsets = (
# ...
)

DjangoAdmin: Accesing the parent instance within an Inline admin

I have a Django admin class which declares an inlines iterable. Something like:
#admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
...
...
inlines = [CategoryModifiersInline,]
...
...
Then I have an Inline admin class like this:
class CategoryModifiersInline(admin.TabularInline):
model = Category.modifiers.through
fk_name = 'category'
extra = 1
def formfield_for_foreignkey(self, db_field, request, **kwargs):
qs = Product.objects.filter(is_modifier=True).filter(active=True)
kwargs['queryset'] = qs
return super(CategoryModifiersInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Where I filter the queryset for the foreign key based on some business requirement.
This inline is only showed to the user in the change view, that means, when an object of the class Category is created and the user wants to add modifiers to it, never in the add view.
What I want to do, is filtering the foreign key by one of the attributes of the Category model, I mean, I want to access the parent object from the formfield_for_foreignkey method.
Does anyone know a way to achieve that?
Well I found a similar question here in StackOverflow, and used the method described there to solve it.
It uses the parent_model attribute from inlines, and the resolve method from django.core.urlresolvers to get the instance based in the url.
Here's the code:
def get_object(self, request):
resolved = resolve(request.path_info)
if resolved.args:
return self.parent_model.objects.get(pk=resolved.args[0])
return None
Then I would call the get_object method inside of my formfield_from_foreignkey method to get the instance of the object I want to use as a filter.
Hope it helps!

Field Level Permission Django

Today i came up with a requirement where i need to implement field level permission so looking for the best possible way.
class ABC(models.Model):
field1 = .....
field2 = .....
field3 = .....
Create two groups(A and B) and assigned permission that both one can add/edit/delete
and the other can only add/edit. But now need some help in this :-
I want if the first group user logs in in the admin he should be able to see all the three fields but if second group user logs in they should only see field1.
I want this in django admin as i need to perform some manipulations after these.My django version is 1.3
Thanks in advance
In your admin.py
class ABCAdmin(admin.ModelAdmin):
fields = [.....] # here comes the fields open to all users
def change_view(self, request, object_id, extra_context=None): # override default admin change behaviour
if request.user in gruop2: # an example
self.fields.append('field2') # add field 2 to your `fields`
self.fields.append('field3') # add field 3 to your `fields`
You can use the docs to see what is available. Above is an example taken from one of my usages. You may also need to define change_view and add_view too.
Just in case someone else stumble about this, I had some issues with the given accepted answer. Every time the view got refreshed, it appended the fields over and over again. As well to the desired restricted view, where it shouldn't appear.
So, according to the docs, I made it working as follows:
Creating a custom ModelForm
class AbcStaffForm(ModelForm):
class Meta:
model = Abc
exclude = ["manager", "foo", "bar",]
Overwrite get_form() in AbcModelAdmin & refered the custom ModelForm
class AbcAdmin(admin.ModelAdmin):
# some other code
# ...
def get_form(self, request, obj=None, **kwargs):
if not request.user.is_superuser:
kwargs['form'] = AbcStaffForm # ModelForm
return super().get_form(request, obj, **kwargs)
You can also override readonly_fields in changeform_view.
Try this in admin.py
class ABCAdmin(admin.ModelAdmin):
def changeform_view(self, request, *args, **kwargs)
self.readonly_fields = list(self.readonly_fields)
if request.user in group: #or another condition
self.readonly_fields.append('field2')
return super(ABCAdmin, self).changeform_view(request, *args, **kwargs)
Overwrite get_fields() in ABCAdmin (group B cannot view "is_on" field):
class ABCAdmin(admin.ModelAdmin):
fields = ['name', 'title', 'price', 'is_on', 'create_time']
def get_fields(self, request, obj=None):
if request.user in groupB:
if 'is_on' not in self.fields:
self.fields.append('is_on')
else:
if 'is_on' in self.fields:
self.fields.remove('is_on')
return super(ABCAdmin, self).get_fields(request, obj)

How can I rename a column label in Django Admin for a field that is a method//property?

I am trying to redefine my admin page for the auth.User model.
Everything is working properly, except for one thing. Check the code below:
from django.contrib import admin
from django.contrib.auth.models import User
from access.models import UserProfile
class UserProfileInline(admin.StackedInline):
model = UserProfile
class UserAdmim(admin.ModelAdmin):
inlines = [UserProfileInline,]
list_display = ['id', 'username', 'get_full_name', 'email']
admin.site.unregister(User)
admin.site.register(User, UserAdmim)
As you can see, one of the fields I want to be displayed in the model page listing -- defined by list_display -- is get_full_name. The problem is that the column label in the admin is displayed as Get full name.
My question is simple: can I override this? If so, how?
Thanks for your help.
Set an attribute in your function called short_description to your desired label in your model definition.
# note, this must be done in the class definition;
# not User.get_full_name.short_description
get_full_name.short_description = 'my label'
Alternatively, if you don't want to pollute your model with admin specific code, you can set list_display to a method on the ModelAdmin which takes one argument: the instance. You'll also have to set readonly_fields so that the admin doesn't try to look up this field in your model. I prefix admin fields with _ to differentiate.
class MyAdmin(...):
list_display = ('_my_field',)
readonly_fields = ('_my_field', )
def _my_field(self, obj):
return obj.get_full_name()
_my_field.short_description = 'my custom label'
Update:
Note that this will break default admin ordering. Your admin will no longer sort fields by clicking the label. To enable this functionality again, define an admin_order_field.
def _date_created(self, obj):
return obj.date_created.strftime('%m/%d/%Y')
_date_created.short_description = "Date Created"
_date_created.admin_order_field = 'date_created'
Update 2:
I've written an admin method decorator that simplifies this process, because once I started using highly descriptive verbose method names, setting attributes on the function became massively repetitive and cluttering.
def admin_method_attributes(**outer_kwargs):
""" Wrap an admin method with passed arguments as attributes and values.
DRY way of extremely common admin manipulation such as setting short_description, allow_tags, etc.
"""
def method_decorator(func):
for kw, arg in outer_kwargs.items():
setattr(func, kw, arg)
return func
return method_decorator
# usage
class ModelAdmin(admin.ModelAdmin):
#admin_method_attributes(short_description='Some Short Description', allow_tags=True)
def my_admin_method(self, obj):
return '''<em>obj.id</em>'''

Django: Faking a field in the admin interface?

I have a model, Foo. It has several database properties, and several properties that are calculated based on a combination of factors. I would like to present these calculated properties to the user as if they were database properties. (The backing factors would be changed to reflect user input.) Is there a way to do this with the Django admin interface?
I would suggest you subclass a modelform for Foo (FooAdminForm) to add your own fields not backed by the database. Your custom validation can reside in the clean_* methods of ModelForm.
Inside the save_model method of FooAdmin you get the request, an instance of Foo and the form data, so you could do all processing of the data before/after saving the instance.
Here is an example for a model with a custom form registered with django admin:
from django import forms
from django.db import models
from django.contrib import admin
class Foo(models.Model):
name = models.CharField(max_length=30)
class FooAdminForm(forms.ModelForm):
# custom field not backed by database
calculated = forms.IntegerField()
class Meta:
model = Foo
class FooAdmin(admin.ModelAdmin):
# use the custom form instead of a generic modelform
form = FooAdminForm
# your own processing
def save_model(self, request, obj, form, change):
# for example:
obj.name = 'Foo #%d' % form.cleaned_data['calculated']
obj.save()
admin.site.register(Foo, FooAdmin)
Providing initial values for custom fields based on instance data
(I'm not sure if this is the best solution, but it should work.)
When a modelform for a existing model instance in the database is constructed, it gets passed this instance. So in FooAdminForm's __init__ one can change the fields attributes based on instance data.
def __init__(self, *args, **kwargs):
super(FooAdminForm, self).__init__(*args, **kwargs)
# only change attributes if an instance is passed
instance = kwargs.get('instance')
if instance:
self.fields['calculated'].initial = (instance.bar == 42)
It's easy enough to get arbitrary data to show up in change list or make a field show up in the form: list_display arbitrarily takes either actual model properties, or methods defined on the model or the modeladmin, and you can subclass forms.ModelForm to add any field type you'd like to the change form.
What's far more difficult/impossible is combining the two, i.e. having an arbitrary piece of data on the change list that you can edit in-place by specifying list_editable. Django seems to only accept a true model property that corresponds to a database field. (even using #property on the method in the model definition is not enough).
Has anyone found a way to edit a field not actually present on the model right from the change list page?
In the edit form, put the property name into readonly_fields (1.2 upwards only).
In the changelist, put it into list_display.
You can use the #property decorator in your model (Python >= 2.4):
class Product(models.Model):
#property
def ranking(self):
return 1
"ranking" can then be used in list_display:
class ProductAdmin(admin.ModelAdmin):
list_display = ('ranking', 'asin', 'title')

Categories

Resources