I have a django site with lots of models and forms. I have many custom forms and formsets and inlineformsets and custom validation and custom querysets. Hence the add model action depends on forms that need other things, and the 'add model' in the django admin throughs a 500 from a custom queryset.
Is there anyway to disable the 'Add $MODEL' functionality for a certain models?
I want /admin/appname/modelname/add/ to give a 404 (or suitable 'go away' error message), I don't want the 'Add $MODELNAME' button to be on /admin/appname/modelname view.
Django admin provides a way to disable admin actions (http://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/#disabling-actions) however the only action for this model is 'delete_selected'. i.e. the admin actions only act on existing models. Is there some django-esque way to do this?
It is easy, just overload has_add_permission method in your Admin class like so:
class MyAdmin(admin.ModelAdmin):
def has_add_permission(self, request, obj=None):
return False
I think this will help you..
below code must be in admin.py file
#admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ('name', )
list_filter = ('name', )
search_fields = ('name', )
list_per_page = 20
# This will help you to disbale add functionality
def has_add_permission(self, request):
return False
# This will help you to disable delete functionaliyt
def has_delete_permission(self, request, obj=None):
return False
In additon to the above as posted by
# This will help you to disable change functionality
def has_change_permission(self, request, obj=None):
return False
By default syncdb creates 3 security permissions for each model:
Create (aka add)
Change
Delete
If your logged in as Admin, you get EVERYTHING no matter what.
But if you create a new user group called "General Access" (for example) then you can assign ONLY the CHANGE and DELETE permissions for all of your models.
Then any logged in user that is a member of that group will not have "Create" permission, nothing related to it will show on the screen.
Just copy code from another answer
# In admin
# make the related field can't be added
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
form.base_fields['service'].widget.can_add_related = False
return form
In my case I use inline
# In inline formset e.g. admin.TabularInline
# disable all
def get_formset(self, request, obj=None, **kwargs):
formset = super().get_formset(request, obj, **kwargs)
service = formset.form.base_fields['service']
service.widget.can_add_related = service.widget.can_change_related = service.widget.can_delete_related = False
return formset
in service = formset.form.base_fields['service']
base_fields is the fields defined in model
if defined in the form use:
product = formset.form.declared_fields['product']
see also
This is a too much delayed answer; Just posting this as if anyone is finding the same solution.
In admin.py file you can do the following:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = '__all__'
class MyModelAdmin(admin.ModelAdmin):
form = QuestionTrackAdminForm
list_display = ['title', 'weight']
readonly_fields = ['title', 'weight']
admin.site.register(MyModel, MyModelAdmin)
Here, "readonly_fields" does the magic. Thanks.
Related
readonly_fields function works properly when I use it with individual models, but it doesn't work with models which are tabularly inlined.
Could somebody help in understanding how to mark fields read only when we deal with models inlined to each other on admin page ?
Thanks.
If you just want to set a readonly field on the inline, you can do:
class SomethingInline(admin.TabularInline):
model = Something
extra = 0
readonly_fields = ('field1',)
If you want to make the entire inline formset readonly on the parent form, you can try this:
class SomethingInline(admin.TabularInline):
model = Something
extra = 0
# Set all your fields here:
readonly_fields = ('field1', 'field2', 'field3')
# Or instead return all your fields here if this should be conditional:
def get_readonly_fields(self, request, obj=None):
return ('field1', 'field2', 'field3')
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
In the last example it will still render all the values for existing inline items, but you cannot add/edit/remove from the interface. this would in effect make the entire formset readonly.
Note: I did not override has_change_permission() to return False, because that would prevent the existing items from being shown.
If you don't want to specify all your fields manually, implement get_readonly_fields() from one of the solutions here: Django admin - make all fields readonly
I am developing a multi-tenant app in Django. In the Django admin, some querysets are filtered based on the user, using get_queryset().
Up till now, when a user updated an object from the Django change form, I would validate the data by creating a ModelAdmin form using a factory function to capture the HttpRequest object, then ensure that the Guest object's user was the current user:
EXAMPLE
models.py
class Guest(models.Model):
guest_name = models.CharField(max_length=64)
user = models.ForeignKey(User, on_delete=models.CASCADE)
admin.py
#admin.register(Guest)
class GuestAdmin(admin.ModelAdmin):
def get_queryset(self, request)
qs = super().get_queryset(request)
return qs.filter(user=request.user)
def get_form(self, request, obj=None, **kwargs):
self.form = _guest_admin_form_factory(request)
return super().get_form(request, obj, **kwargs)
forms.py
def _guest_admin_form_factory(request):
class GuestAdminForm(forms.ModelForm):
class Meta:
model = Guest
exclude = []
def clean_user(self):
user = self.cleaned_data.get('user', None)
if not user:
return user
if user != request.user:
raise forms.ValidationError('Invalid request.')
return user
return GuestAdminForm
It occurred to me that Django might use the get_queryset() method to validate this for me, since some simple logging showed that the method is called twice when an object gets updated from the change form.
Is this the case, or do I need to stick to validating through a ModelAdmin form?
The documented way to do this is to define has_change_permission():
#admin.register(Guest)
class GuestAdmin(admin.ModelAdmin):
def get_queryset(self, request):
return super().get_queryset(request).filter(user=request.user)
def has_change_permission(self, request, obj=None):
return (obj is None or obj.user == request.user)
No need to muck about with the form.
I'm tring write a view to administrator update a password of another user, using Class Based Views and model SetPasswordForm of Django.
My views.py
class UserSetPasswordUpdateView(GroupRequiredMixin, FormView):
form_class = forms.SetPasswordForm
model = User
template_name = 'app/admin/object_update.html'
success_url = reverse_lazy('portal:admin_user')
group_required = u"Administrator"
def get_form_kwargs(self):
kwargs = super(UserSetPasswordUpdateView, self).get_form_kwargs()
kwargs['user'] = User.objects.filter(pk=self.kwargs['pk'])
return kwargs
update_change_password = UserSetPasswordUpdateView.as_view()
My urls.py
url(r'^app/admin/update-user-pass/(?P<pk>[0-9]+)$', update_views.update_change_password, name='update_change_password'),
And don't show any errors, just go to success_url, but the password don't updated.
Your view is based on FormView. This doesn't have any knowledge of model forms, and doesn't do anything with the data other than check that it is valid. SetPasswordForm changes the password when the form is saved, but this view never does this.
You could override form_valid to call form.save() explicitly, but it would be better to use a more appropriate base class such as UpdateView which will do that for you.
I need to add the field with link to the model view on my site to the django admin view.
When I add field name to the list_display and define method for rendering this url:
class SetAdmin(admin.ModelAdmin):
list_display = ['many other fields', 'show_set_url']
def show_set_url(self, obj):
return 'Set' # render depends on other fields
It shows in Sets list in django admin, but not in model form.
How can I fix this and add link to the Sets Form in django admin?
I've tried also to create custom form for this model:
from core.models import Set
from django import forms
class SetAdminForm(forms.Form):
class Meta:
model = Set
def __init__(self, *args, **kwargs):
super(SetAdminForm, self).__init__(*args, **kwargs)
self.fields['foo'] = forms.IntegerField(label=u"Link")
But there is now visible effect in form.
You can accomplish what you're trying by overriding ModelAdmin but you also need to override ModelAdmin.get_fieldsets. This answer might help you out. The OP in the link has a similar problem as well.
Edit: If you don't want an editable field you can try overriding ModelAdmin.get_readonly_fields. Also check here for more attributes to override.
You can create dynamic fields and fieldset using the form meta class. Sample code is given below. Add the loop logic as per you requirements.
class CustomAdminFormMetaClass(ModelFormMetaclass):
"""
Metaclass for custom admin form with dynamic field
"""
def __new__(cls, name, bases, attrs):
for field in myloop: #add logic to get the fields
attrs[field] = forms.CharField(max_length=30) #add logic to the form field
return super(CustomAdminFormMetaClass, cls).__new__(cls, name, bases, attrs)
class CustomAdminForm(six.with_metaclass(CustomAdminFormMetaClass, forms.ModelForm)):
"""
Custom admin form
"""
class Meta:
model = ModelName
fields = "__all__"
class CustomAdmin(admin.ModelAdmin):
"""
Custom admin
"""
fieldsets = None
form = CustomAdminForm
def get_fieldsets(self, request, obj=None):
"""
Different fieldset for the admin form
"""
self.fieldsets = self.dynamic_fieldset(). #add logic to add the dynamic fieldset with fields
return super(CustomAdmin, self).get_fieldsets(request, obj)
def dynamic_fieldset(self):
"""
get the dynamic field sets
"""
fieldsets = []
for group in get_field_set_groups: #logic to get the field set group
fields = []
for field in get_group_fields: #logic to get the group fields
fields.append(field)
fieldset_values = {"fields": tuple(fields), "classes": ['collapse']}
fieldsets.append((group, fieldset_values))
fieldsets = tuple(fieldsets)
return fieldsets
You have to add it to the readonly_fields list.
class SetAdmin(admin.ModelAdmin):
list_display = ['many other fields', 'show_set_url']
readonly_fields = ['show_set_url']
def show_set_url(self, obj):
return 'Set' # render depends on other fields
Relevant documentation.
I had trouble with all of these answers in Django 1.9 for inline models.
Here is a code snippet that accomplished dynamic fields for me. In this example, I'm assuming you already have a modal called ProductVariant which contains a foreign key relationship to a model called Product:
class ProductVariantForm(forms.ModelForm):
pass
class ProductVariantInline(admin.TabularInline):
model = ProductVariant
extra = 0
def get_formset(self, request, obj=None, **kwargs):
types = ( (0, 'Dogs'), (1, 'Cats'))
#Break this line appart to add your own dict of form fields.
#Also a handy not is you have an instance of the parent object in obj
ProductVariantInline.form = type( 'ProductVariantFormAlt', (ProductVariantForm, ), { 'fancy_select': forms.ChoiceField(label="Animals",choices=types)})
formset = super( ProductVariantInline, self).get_formset( request, obj, **kwargs)
return formset
class ProductAdmin(admin.ModelAdmin):
inlines = (ProductVariantInline, )
My specific use case is ProductVariant has a many to many relationship that should only have limited selections based on business logic grouping of the entries. Thus my need for custom dynamic fields in the inline.
I have a model that has a user field that needs to be auto-populated from the currently logged in user. I can get it working as specified here if the user field is in a standard ModalAdmin, but if the model I'm working with is in an InlineModelAdmin and being saved from the record of another model inside the Admin, it won't take.
Here's what I think is the best solution. Took me a while to find it... this answer gave me the clues: https://stackoverflow.com/a/24462173/2453104
On your admin.py:
class YourInline(admin.TabularInline):
model = YourInlineModel
formset = YourInlineFormset
def get_formset(self, request, obj=None, **kwargs):
formset = super(YourInline, self).get_formset(request, obj, **kwargs)
formset.request = request
return formset
On your forms.py:
class YourInlineFormset(forms.models.BaseInlineFormSet):
def save_new(self, form, commit=True):
obj = super(YourInlineFormset, self).save_new(form, commit=False)
# here you can add anything you need from the request
obj.user = self.request.user
if commit:
obj.save()
return obj
I know I'm late to the party, but here's my situation and what I came up with, which might be useful to someone else in the future.
I have 4 inline models that need the currently logged in user.
2 as a created_by type field. (set once on creation)
and the 2 others as a closed_by type field. (only set on condition)
I used the answer provided by rafadev and made it into a simple mixin which enables me to specify the user field name elsewhere.
The generic formset in forms.py
from django.forms.models import BaseInlineFormSet
class SetCurrentUserFormset(forms.models.BaseInlineFormSet):
"""
This assume you're setting the 'request' and 'user_field' properties
before using this formset.
"""
def save_new(self, form, commit=True):
"""
This is called when a new instance is being created.
"""
obj = super(SetCurrentUserFormset, self).save_new(form, commit=False)
setattr(obj, self.user_field, self.request.user)
if commit:
obj.save()
return obj
def save_existing(self, form, instance, commit=True):
"""
This is called when updating an instance.
"""
obj = super(SetCurrentUserFormset, self).save_existing(form, instance, commit=False)
setattr(obj, self.user_field, self.request.user)
if commit:
obj.save()
return obj
Mixin class in your admin.py
class SetCurrentUserFormsetMixin(object):
"""
Use a generic formset which populates the 'user_field' model field
with the currently logged in user.
"""
formset = SetCurrentUserFormset
user_field = "user" # default user field name, override this to fit your model
def get_formset(self, request, obj=None, **kwargs):
formset = super(SetCurrentUserFormsetMixin, self).get_formset(request, obj, **kwargs)
formset.request = request
formset.user_field = self.user_field
return formset
How to use it
class YourModelInline(SetCurrentUserFormsetMixin, admin.TabularInline):
model = YourModel
fields = ['description', 'closing_user', 'closing_date']
readonly_fields = ('closing_user', 'closing_date')
user_field = 'closing_user' # overriding only if necessary
Be careful...
...as this mixin code will set the currently logged in user everytime for every user. If you need the field to be populated only on creation or on specific update, you need to deal with this in your model save method. Here are some examples:
class UserOnlyOnCreationExampleModel(models.Model):
# your fields
created_by = # user field...
comment = ...
def save(self, *args, **kwargs):
if not self.id:
# on creation, let the user field populate
self.date = datetime.today().date()
super(UserOnlyOnCreationExampleModel, self).save(*args, **kwargs)
else:
# on update, remove the user field from the list
super(UserOnlyOnCreationExampleModel, self).save(update_fields=['comment',], *args, **kwargs)
Or if you only need the user if a particular field is set (like boolean field closed) :
def save(self, *args, **kwargs):
if self.closed and self.closing_date is None:
self.closing_date = datetime.today().date()
# let the closing_user field set
elif not self.closed :
self.closing_date = None
self.closing_user = None # unset it otherwise
super(YourOtherModel, self).save(*args, **kwargs) # Call the "real" save() method.
This code could probably be made way more generic as I'm fairly new to python but that's what will be in my project for now.
Only the save_model for the model you're editing is executed, instead you will need to use the post_save signal to update inlined data.
(Not really a duplicate, but essentially the same question is being answered in Do inline model forms emmit post_save signals? (django))
I had a similar issue with a user field I was trying to populate in an inline model. In my case, the parent model also had the user field defined so I overrode save on the child model as follows:
class inline_model:
parent = models.ForeignKey(parent_model)
modified_by = models.ForeignKey(User,editable=False)
def save(self,*args,**kwargs):
self.modified_by = self.parent.modified_by
super(inline_model,self).save(*args,**kwargs)
The user field was originally auto-populated on the parent model by overriding save_model in the ModelAdmin for the parent model and assigning
obj.modified_by = request.user
Keep in mind that if you also have a stand-alone admin page for the child model you will need some other mechanism to keep the parent and child modified_by fields in sync (e.g. you could override save_model on the child ModelAdmin and update/save the modified_by field on the parent before calling save on the child).
I haven't found a good way to handle this if the user is not in the parent model. I don't know how to retrieve request.user using signals (e.g. post_save), but maybe someone else can give more detail on this.
Does the other model save the user? In that case you could use the post_save signal to add that information to the set of the inlined model.
Have you tried implementing custom validation in the admin as it is described in the documentation? Overriding the clean_user() function on the model form might do the trick for you.
Another, more involved option comes to mind. You could override the admin template that renders the change form. Overriding the change form would allow you to build a custom template tag that passes the logged in user to a ModelForm. You could then write a custom init function on the model form that sets the User automatically. This answer provides a good example on how to do that, as does the link on b-list you reference in the question.