Django admin inline - show only when changing object - python

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 = (
# ...
)

Related

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!

Django hide field when creating new object

I want to hide field name when user is creating new object, but it must be visible if user wants to edit this object.
I tried exclude method but it makes field invisible when i try to edit this field. for example i want to hide status field.
class Toys(BaseModel):
name = models.CharField(max_length=255)
tags = models.ManyToManyField(Tag, related_name='Item_tags')
price = models.CharField(max_length=255)
status = models.BooleanField(default=False)
In the model admin that you register in your admin.py file you can overload the get_form(self, request, obj=None, **kwargs) method. As you can see it takes the obj argument, it is None only on add (not None on change).
From there you could mess around with the form to exclude the form field "name" from it only if the obj is None.
In Django 1.10 that method is in django.contrib.admin.options.ModelAdmin.get_form.
EDIT 1 (this is by far not the best solution)
I can't give you a full solution here, but you can start with something like:
# admin.py
from django.contrib import admin
from models import Toys
class ToysModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
# all the code you have in the original
# django.contrib.admin.options.ModelAdmin.get_form
# up to the last try except
if obj is not None:
defaults['fields'] = ('tags', 'price', 'status', )
try:
return modelform_factory(self.model, **defaults)
except FieldError as e:
raise FieldError(
'%s. Check fields/fieldsets/exclude attributes of class %s.'
% (e, self.__class__.__name__)
)
admin.site.register(Toys, ToysModelAdmin)
EDIT 2 (this is a better example)
# admin.py
from collections import OrderedDict
from django.contrib import admin
from models import Toys
class ToysModelAdmin(admin.ModelAdmin):
# this is not perfect as you'll need to keep track of your
# model changes also here, but you won't accidentally add
# a field that is not supposed to be editable
_add_fields = ('tags', 'price', 'status', )
def get_form(self, request, obj=None, **kwargs):
model_form = super(ToysModelAdmin, self).get_form(
request, obj, **kwargs
)
if obj is None:
model_form._meta.fields = self._add_fields
model_form.base_fields = OrderedDict(**[
(field, model_form.base_fields[field])
for field in self._add_fields
])
return model_form
I had same problem.
I wanted to exclude some columns only in create form.
You can override get_exclude() method
from django.contrib import admin
from . import models
#admin.register(models.Toys)
class ToysAdmin(admin.ModelAdmin):
def get_exclude(self, request, obj=None):
exclude = list(super().get_exclude(request, obj) or [])
if obj==None:
# columns to exclude when it's create form
exclude += ["status","column_to_hide_on_create"]
return tuple(set(exclude))

Dynamically setting readonly_fields in django admin

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

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)

Django custom user edit form: get_fieldsets() error

In Django's admin.py, why, even though this works:
class StudentAdmin(UserAdmin):
add_form = UserCreationForm
form = CustomChangeForm
fieldsets = UserAdmin.fieldsets
these do not?
class StudentAdmin(UserAdmin):
add_form = UserCreationForm
form = CustomChangeForm
def get_fieldsets(self, request, obj = None):
return UserAdmin.fieldsets
or
class StudentAdmin(UserAdmin):
add_form = UserCreationForm
form = CustomChangeForm
def get_fieldsets(self, request, obj = None):
return super(UserAdmin, self).get_fieldsets(request, obj)
Shouldn't they be equivalent?
The second set gives me an exception u"Key 'password' not found in Form", while the first one works fine.
The point, in the long term, is obviously to get more specific things working, but first I'd like to figure out what I got wrong so far.
Similarly, adding:
inlines = (MyInline,)
to the class works. But adding this:
def get_inline_instances(self, request, obj=None):
return (MyInline,)
throws the exception: unbound method get_formset() must be called with MyInline instance as first argument (got WSGIRequest instance instead). To the extent of my understanding, these two should also be equivalent.
Thanks.

Categories

Resources