Django Admin - Make previous inline forms uneditable - python

I have an inline form in Django Admin. When the user edits the modelform all previously filled inline forms are also listed. I just want to allow users to view previously filled inline forms and make them uneditable. But the user can add another form.
I tried using editable=False but this doesn't allow me to fill new form.

I think https://code.djangoproject.com/ticket/15602 prevents you from doing what you want.
If you split it into two inline admins, one for listing and one for adding, you can achieve what you want, but I don't like this solution:
class CommentListInline(admin.TabularInline):
model = Comment
fields = ('comment',)
readonly_fields = fields
extra = 0
can_delete = False
def has_add_permission(self, request):
return False
class CommentAddInline(admin.TabularInline):
model = Comment
fields = ('comment',)
extra = 1
can_delete = False
def has_change_permission(self, request, obj=None):
return False
class PageAdmin(admin.ModelAdmin):
inlines = [CommentListInline, CommentAddInline]

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

How to create custom permission for specific fields in model?

I have created a model and I want to create an user group that has the permission of edit only one field of this model.
For example if I have Car model with some fields (for example model, brand, registration) and one basic user, I want that the user is able to change only registration field.
It is possible to do that?
models.py:
class Model(models.Model):
class Meta:
# your custom permissions
permissions = (
('model_can_edit_title', _('Can Edit Title')),
)
do makemigration and migrate.
admin.py:
def get_readonly_fields(self, request, obj=None):
# you can check user type. and make decision
if 'app.model_can_edit_title' in request.user.user.get_all_permissions():
# user can update all fields
return []
# user can't update title field, title will be read only.
return ['title',]
you can use
def get_exclude(self, request, obj=None):
method also.

Ignore inline model when saving

I've been looking on the documentation and stackoverflow/forums for a way to ignore the inline children of a model when I save it in django admin. I've been searching for a few days and I can't seem to find an answer.
I have a normal tabularinline object:
class UserOrdersAdmin(admin.TabularInline):
model = Order
classes = ['collapse']
And a normal User admin registration:
class UserAdmin(BaseUserAdmin):
inlines = (UserOrdersAdmin, UserSettingsAdmin)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
What I simply want is when I press save within the User "change view", it will ignore the inline "UserOrderAdmin" which is inline to UserAdmin.
From your response on my comment I am getting the idea you want to show some extra information in the admin, that is not editable. This can be achieved using readonly_fields in the Inline, for completeness you should also set the max_num to 0, because otherwise you can add empty inlines.
You could enter all fields manually or use something like given in this answer: https://stackoverflow.com/a/42877484/2354734
The end result would look something like this.
class UserOrdersAdmin(admin.TabularInline):
model = Order
classes = ['collapse']
max_num = 0
def get_readonly_fields(self, request, obj=None):
return list(set(
[field.name for field in self.opts.local_fields] +
[field.name for field in self.opts.local_many_to_many]
))
For completeness of the answer also a link to the documentation
Try this:
class UserOrdersAdmin(admin.TabularInline):
model = Order
classes = ['collapse']
extra = 0

Django Inline Admin delete missing

Here is my admin.py
class BooksInlineAdmin(admin.TabularInline):
model = Book
extra = 2
max_num = 4
def has_delete_permission(self, request, obj):
return True
class AuthorAdmin(admin.ModelAdmin):
list_display=("name", "phone")
inlines = [BooksInlineAdmin]
admin.site.register(Author, AuthorAdmin)
In admin the auto generated inlines are missing "delete". If I add one by clicking "add another" It is having delete button. why is it so? How can i enable delete for all the inline forms. (I am using django 1.6)
The extra=2 means you always see at least 2 inlines for this model,
So delete button is removed, try changing the extra value

Django InlineModelAdmin - set inline field from request on save (set user field automatically) (save_formset vs save_model)

I have two models, a MainModel and a related InlineModel that i'd like to show as an inline in the admin. This InlineModel can be used for, say, making notes about the model and should track the logged in admin user making changes. While this seems simple (and indeed, the docs show an example for this when the user field is part of the MainModel), I can't seem to grasp it when the field is on the Inline.
To be specific, my goal is:
User edits MainModel
User adds an InlineModel, not filling in the user field
User presses save
Code fills in the user field for newly created InlineModel instances
(Bonus! user field is readonly for existing instances and hidden for new inlines)
And my questions:
Is this correct? Its too bas save_model isn't called for InlineModelAdmin instances
Does doing it this way allow me to save without causing an error? (user is required, validation flags it)
How can I hide the user input field for new inlines, and have it readonly for existing inlines?
Here are my current ideas:
#models.py
class MainModel(models.Model):
some_info = models.IntegerField()
class InlineModel(models.Model):
main = models.ForeignKey(MainModel)
data = models.CharField(max_length=255)
user = models.ForeignKey('auth.User')
#admin.py
class InlineModelInline(admin.TabularInline):
model = InlineModel
fields = ('data', 'user')
#readonly_fields = ('data', 'user') #Bonus question later
class MainModelAdmin(admin.ModelAdmin):
list_display = ('id', 'some_info')
inlines = [InlineModelInline]
#def save_model(self, request, obj, form, change):
#http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model
#Only called for MainModel, not for any of the inlines
#Otherwise, would be ideal
def save_formset(self, request, form, formset, change):
#http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_formset
#Experimenting showd this is called once per formset (where the formset is a group of inlines)
#See code block at http://code.djangoproject.com/browser/django/tags/releases/1.2.1/django/contrib/admin/options.py#L894
if not isinstance(formset.model, InlineModel):
return super(MainModelAdmin, self).save_formset(request, form, formset, change)
instances = formset.save(commit=False)
for instance in instances:
if not instance.pk:
instance.user = request.user
instance.save()
formset.save_m2m()
I have solved the first half of my question:
def save_formset(self, request, form, formset, change):
if formset.model != InlineModel:
return super(MainModelAdmin, self).save_formset(request, form, formset, change)
instances = formset.save(commit=False)
for instance in instances:
if not instance.pk:
instance.user = request.user
instance.save()
formset.save_m2m()
Now i'm interested in the bonus behavior:
I'm required to select a user when adding a new inline due to validation rules. My best guess is to not include the 'user' field in my InlineModelInline.fields tuple, but then this won't show the author for existing InlineModel instances. (Edit: adding 'user' to readonly_fields works here)
(Edit) How can I make the existing inlines render 'data' as readonly, but still be able to edit it when adding a new inline?
It worked for me. This approach won't allow me to delete Inline items.
def save_formset(self, request, form, formset, change):
for form in formset.forms:
form.instance.user = request.user
formset.save()
To answer the Bonus Question: "How can I make the existing inlines render 'data' as readonly, but still be able to edit it when adding a new inline?":
I use two inlines for the same model:
#admin.py
class InlineModelInline(admin.TabularInline):
model = InlineModel
extra = 1
max_num = 1
#admin.py
class InlineModelExistingInline(admin.TabularInline):
model = InlineModel
readonly_fields = ('data', 'user') #All Fields here except pk
can_delete = False
extra = 0
max_num = 0
class MainModelAdmin(admin.ModelAdmin):
...
inlines = [InlineModelInline, InlineModelExistingInline]
...

Categories

Resources