In my django admin section, I'd like to show different versions of the admin page depending on what kind of user is currently logged in. I can think of a couple ways this might work, but haven't figured out how to do any of them.
Perhaps I could put logic into the admin.ModelAdmin to look at the current user and change the 'exclude' field dynamically. Does that work? Or maybe run different custom templates based on who's logged in, and have the templates include / exclude the fields as appropriate.
I could register two versions of the admin.ModelAdmin class, one for each type of user, and maybe restrict access through permissions? But the permissions system seems to believe fairly deeply in one set of permissions per model class so I'm not sure how to change that.
I could grab a couple of the widgets that are used in rendering the admin page templates, and include them in my own page that does the one specific job I need powerful users to be able to do.
I could set up multiple AdminSites and restrict access to them through the url / view system. But then I'm not sure how to register different admin.ModelAdmin classes with the different AdminSites.
Any advice on this would be appreciated.
Answer
Thanks for the hint. Here's how I did it...
def get_form(self, request, obj=None, **kwargs):
"""This dynamically inserts the "owners" field into the exclude list
if the current user is not superuser.
"""
if not request.user.is_superuser:
if self.exclude:
self.exclude.append('owners')
else:
self.exclude = ['owners']
else:
# Necessary since Admin objects outlive requests
try:
self.exclude.remove('owners')
except:
pass
return super(OwnersModelAdmin,self).get_form(request, obj=None, **kwargs)
There are quite a few hooks provided in the ModelAdmin class for this sort of thing.
One possibility would be to override the get_form method. This takes the request, as well as the object being edited, so you could get the current user from there, and return different ModelForms dependent on the user.
It's worth looking at the source for ModelAdmin - it's in django.contrib.admin.options - to see if overriding this or any other other methods might meet your needs.
Related
I have a model Order and model Invoice. The Order has
invoice = models.OneToOneField('Invoice', related_name='order', on_delete=models.CASCADE, blank=True, null=True)
Invoice object is created right after order object is created an assigned to it. Admin has to edit the invoice (price field) before customer pays.
The problem is that Django-admin allows admin to change this field too (bottom of the image), which I can't risk but I want to let the pencil icon (change attributes of the invoice).
Is it possible to do that? When I add invoice to readonly_fields in OrderAdmin, Admin can't edit those attributes like invoice.price etc.
EDIT:
So I want admin to be able to edit attributes of the invoice. Not add nor choose from dropdown.
One option would be to provide a custom template for this view. The docs say that you can specify a path to a custom template using ModelAdmin.change_form_template.
Here is a section of the docs that talk about how to override a template: https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#overriding-vs-replacing-an-admin-template
Though this is not the optimal solution, you could probably use Javascript to hide/disable the parts you don't want.
Finally, you may want to consider your usage of the Django admin:
The admin’s recommended use is limited to an organization’s internal
management tool. It’s not intended for building your entire front end
around.
The admin has many hooks for customization, but beware of trying to
use those hooks exclusively. If you need to provide a more
process-centric interface that abstracts away the implementation
details of database tables and fields, then it’s probably time to
write your own views.
def has_add_permission(self, request):
return False
Django admin: How to display a field that is marked as editable=False' in the model?
I have this Django project that I'm working on, which won't allow users to select an entry (User entries) on the raw_id_fields popup, if they don't have change permissions (which they can't have at all). That's really weird cause that doesn't happen with the select tag list if I remove the raw_id_fields attribute on my ModelForm class at admins.py.
How can this permission behavior be consistent if it changes according to a different interface setting? I mean, user only have permission to select users on the form if they are displayed as a select tag. It seems to me that it's a big consistency failure with the way Django permissions was designed, which, in my opinion, should have native can_view permission, in addition to can_|add, change, delete.
While googling around I found a few topics discussing this matter, but all of them end up with some really painful solutions that don't seem straightforward to me. I wonder if something so simple could have a straightforward solution that won't require lots of workarounds.
Here is an example that looks like my actual code:
models.py
class Project(models.Model):
manager = models.ForeignKey(User)
...
admins.py
class ProjectAdmin(admin.ModelAdmin):
raw_id_fields = ['manager',]
...
As you've said, this is really weird. Thats why I've opted to "extend" the django admin to my specific requirements sometimes.
The easiest way to meet this goal is by overriding the has_change_permission of the referenced ModelAdmin
As you have the request object as an argument of the method, you can evaluate:
if the request comes from a raw_id_field or not
if the user has permissions to see that models or not
any other constraint you have
A simple prototype for the method:
def has_change_permission(self, request, obj=None):
if obj is None and '_popup' in request.GET:
return True
return super(MyAdmin, self).has_change_permission(request, obj)
The built-in actions that come with the Django admin generally display a helpful message after they execute at the top, e.g. saying that a new object was added or what have you.
The docs show how to do it with simple actions that can be represented as methods of the custom ModelAdmin. However, with custom actions that need intermediate pages (covered further down on that same page), I am encouraged to pass the user onto another view. That's great, but it means that I no longer have access to the custom ModelAdmin instance in order to call its message_user() method... Or at least I'm not sure how to get it.
Can you tell me how to get a hold of the current ModelAdmin instance or, if there's a better way, how else to display one of those helpful little messages when I'm done in the other view?
To mimic the ModelAdmin.message_user method you only need to do the following:
from django.contrib import messages
messages.info(request, message)
Adding a message is documented here https://docs.djangoproject.com/en/dev/ref/contrib/messages/#adding-a-message and the way ModelAdmin uses it can be seen here: https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L691
Construct a LogEntry and write a custom templatetag to render messages on your intermediat page, for instance:
LogEntry.objects.log_action(
user_id=request.user.id,
content_type_id=ContentType.objects.get_for_model(yourmodel).pk,
object_id=case.id,
object_repr=force_unicode(yourmodel),
action_flag=ADDITION if created else CHANGE)
read more: Django docs (Message Framework)
This question already has answers here:
Django Admin - Disable the 'Add' action for a specific model
(5 answers)
Closed 10 years ago.
Is there a way to remove the "Add" functionality on the Django admin site? For certain entities, I only want the Django admin to be able to view them or change existing ones, but not add new ones.
See: Django Admin - Disable the 'Add' action for a specific model for true solution.
Sure, you can customize admin VERY granularly by following the instructions here -- I believe that what you want can be obtained in part by overriding ModelAdmin.save_model(self, request, obj, form, change) in your own ModelAdmin subclass, to ensure nothing happens on the store when change is false (i.e. an attempt to add rather than change), and in part by overriding ModelAdmin.add_view(self, request, form_url='', extra_context=None) to display an "add view" that makes it very clear to the admin that they're NOT going to be allowed to add object through this route. I haven't actually done the specific admin customization you require, but I've done others and they do seem to work pretty smoothly!
You can customize the permission for each user group from within the admin interface: try going to /admin/auth/group and it should be straightforward from there.
This won't be as granular as the solution offered by the earlier answer, but it will take care of most of your needs without needing to customize the admin.
If you change the permissions to restrict access then you'll still get the plus sign by a FK/MtM field. Clicking that will open a popup window with 'Permission Denied' in it.
You can actually completely remove the plus sign by not simply not registering the model with the admin.
I have a situation where I have predefined categories that I want users to be able to select more than one of. The best way to do this is with a models.ManyToMany field. You can register the model with the admin, enter the data as required and then remove the registration.
An easy effective way is to set max_num=0 for that particular inline.
Satya's suggestion of setting max_num=0 works perfectly.
Per the Django docs on the ModelForm class:
For users with JavaScript-enabled browsers, an "Add another" link is provided to enable any number of additional inlines to be added in addition to those provided as a result of the extra argument.
The dynamic link will not appear if the number of currently displayed forms exceeds max_num, or if the user does not have JavaScript enabled.
and
As with regular formsets, you can use the max_num and extra parameters to modelformset_factory to limit the number of extra forms displayed.
max_num does not prevent existing objects from being displayed
I'm wondering what the common project/application structure is when the user model extended/sub-classed and this Resulting User model is shared and used across multiple apps.
I'd like to reference the same user model in multiple apps.
I haven't built the login interface yet, so I'm not sure how it should fit together.
The following comes to mind:
project.loginapp.app1
project.loginapp.app2
Is there a common pattern for this situation?
Would login best be handled by a 'login app'?
Similar to this question but more specific.
django application configuration
UPDATE
Clarified my use-case above.
I'd like to add fields (extend or subclass?) to the existing auth user model. And then reference that model in multiple apps.
Why are you extending User? Please clarify.
If you're adding more information about the users, you don't need to roll your own user and auth system. Django's version of that is quite solid. The user management is located in django.contrib.auth.
If you need to customize the information stored with users, first define a model such as
class Profile(models.Model):
...
user = models.ForeignKey("django.contrib.auth.models.User", unique=True)
and then set
AUTH_PROFILE_MODULE = "appname.profile"
in your settings.py
The advantage of setting this allows you to use code like this in your views:
def my_view(request):
profile = request.user.get_profile()
etc...
If you're trying to provide more ways for users to authenticate, you can add an auth backend. Extend or re-implement django.contrib.auth.backends.ModelBackend and set it as
your AUTHENTICATION_BACKENDS in settings.py.
If you want to make use of a different permissions or groups concept than is provided by django, there's nothing that will stop you. Django makes use of those two concepts only in django.contrib.admin (That I know of), and you are free to use some other concept for those topics as you see fit.
You should check first if the contrib.auth module satisfies your needs, so you don't have to reinvent the wheel:
http://docs.djangoproject.com/en/dev/topics/auth/#topics-auth
edit:
Check this snippet that creates a UserProfile after the creation of a new User.
def create_user_profile_handler(sender, instance, created, **kwargs):
if not created: return
user_profile = UserProfile.objects.create(user=instance)
user_profile.save()
post_save.connect(create_user_profile_handler, sender=User)
i think the 'project/app' names are badly chosen. it's more like 'site/module'. an app can be very useful without having views, for example.
check the 2008 DjangoCon talks on YouTube, especially the one about reusable apps, it will make you think totally different about how to structure your project.