Django prevent superuser from seeing data in /change/ page - django

I'm using Django 2.1.5 and have been using the list_display in ModelAdmin-based classes to limit what our superusers can see in the admin pages. There is some sensitive data that only the user should have access to.
Say I have a app based on model SensitiveObject with id, name, secret, etc., I can simply exclude secret from the list_display and it will never show up in the Admin page. However, when I browse to /admin/full/myapp/sensitiveobject/ I will have a list of those object IDs and can simply go to /admin/.../sensitiveobject/<id>/change/ which will show all of the fields, including the ones that I excluded from list_display.
Is there a way to limit what I'm seeing in the /change/ endpoint, as well?

The fields attribute of your ModelAdmin is what defines the fields that are displayed on the change form for each object.
You can make this dynamic you can override the method get_fields:
def get_fields(request, obj=None):
fields = [] # Default list of fields
if request.user.passes_some_test():
fields.append() # The secret field
return fields

Related

The relation between a model and it's form in Django, and user authentication

I'm migrating something from an old PHP/apache server to Django. I'm a bit stumped with the 'ModelForm'.
As far as I understand, a "Model" is the abstraction for persistent elements in my website/server - specifically this is something stored physically, say in a database, and defines the fields (read columns) in the DB.
I started moving the authentication part of the site, and discovered models, and specifically the User model (I made an empty User inheriting AbstractUser just in case I will ever need to extend things). Now I want to create a simple two field form, to authenticate login.
The form:
Username (which is a field of User, by default)
Password (Which is not).
Even the 'Username' needs a redefinition in the model form. So my questions:
What is the advantage of the model form (over just a form)? - seems like you're redefining fields anyway, and obviously sometimes adding fields on top of the model.
Specifically for authentication, I probably need to store my salted hash associated with the user somehow, compare my password using that and retrieve the user object. This is something I find very hard to find in the Django docs - they just have too much written on authentication, and not one full code example. Do I put this in the "validate" method of form, retrieving there an object and storing it in a session or something?
If there is a deeper relation between a model form and the associated model, I would like to know as well.
Simple django forms and modelforms have quite differences.
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter']
The above example illustrates that you don't have to write any form field in here. The model form will itself create a form which is based on the attributes provided in the model ('Article' in this example).
If you create a simple django form then it would be something like:
class ArticleForm(forms.Form):
some_field = forms.CharField(some_attrs)
...
The django User model provides you everything you need for authentication. When you want to create users just import django.contrib.auth.models.User and use create method to create objects. Then when you want to authenticate a user use authenticate method.
from django.contrib.auth import authenticate, login
def user_login(request):
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
# after authentication login the user or set cookies or modify the session or some other action can be taken
return HttpResponse("Some response or use render for html page")
username and password will be coming from your post request.
If you want to extend default Django user model you can use django user model as onetoonefield in your extended model.
class AppUser(models.Model):
user = models.OneToOneField(User)
... # other custom fields

Django Admin Reverse Relations

If you are on the django admin page for the model Group. You don't know that there is a reverse relation to user.
Some people (not me) have difficulties with it.
Is there a way to show all reverse relations, so that you can jump to the matching admin pages?
Example:
On admin page for Group I want a link to User (and all other models which refer to it).
This should happen by code, not by hand with templates.
This method doesn't automatically add links to all related models of a Group, but does for all Users related to a Group (so for one related model at a time). With this you'll get an inline view in your Group with the related Users.
You could probably extend this technique to make it automatically work for all related fields.
class UserInline(admin.StackedInline):
model = User
extra = 0
readonly_fields = ('change',)
def change(self, instance):
if instance.id:
# Django's admin URLs are automatically constructed
# based on your Django app and model's name.
change_url = urlresolvers.reverse(
'admin:djangoapp_usermodel_change', args=(instance.id,)
)
return '<a class="changelink" href="{}">Change</a>'.format(change_url)
else:
return 'Save the group first before editing the user.'
change.allow_tags = True
class GroupAdmin(admin.ModelAdmin):
list_display = ('name',)
inlines = (UserInline,)
You might also be interested in this extension I created for Django admin pages to link to related objects:
https://github.com/gitaarik/django-admin-relation-links
It's quite easy to use and makes the admin a lot more convenient to use :).

admin site doesn't show object fields

I have a model with a PointField from django.contrib.gis.db.models . This somehow doesn't let the admin site show the objects with a nice table of fields and values. Instead it displays one field named after the model name. The values are just a bunch of ' object'. With the name of the model. If I click the object I can edit it fine. It would be nice however to be able to filter and see the field values at the admin/ page itself.
Since PointField does not have a __unicode__ attribute, for the proper name to show up, you can register a new admin model object.
Now, in the admin's list_display,
class PointFieldAdmin(admin.ModelAdmin):
list_display = ('name', 'field_x', 'field_y', ...)
admin.register(PointField, PointFieldAdmin)
More on admin models registering here

Creating custom users in Django and customizing the admin

I need to create custom users in my app.
In the example given in the doc
class CustomUser(models.Model):
user = models.OneToOneField(User)
#custom fields
a user must exists before creating a CustomUser.
What I want to do is to create automatically a User when I create a CustomUser.
In the CustomUser admin (only visible by the superuser), I'd like to have only the custom fields and a few fields from the User model, as well as some form to allow the superuser to change the password for existing instance.
Anybody could help?
The first part of your question is easy, you can use a signal:
def create_custom_user(sender, instance, created, **kwargs):
if created:
custom_user, created = CustomUser.objects.get_or_create(user=instance)
post_save.connect(create_custom_user, sender=User)
As for the second part, theres already a change password form in the admin. To filter out the displayed fields you can create a CustomUserAdmin and register it together with the model. It's pretty self explaining in the django docs.
django docs: list_display

How do I add custom fields to a User when using django-social-auth

I need to add a BooleanField and a ManyToManyField to my users. I'm using django-social-auth. It seems I could use 'CustomUser'. I guess that's what it's for, but how do I take it into use?
I would need to know:
where to define these new fields
How to add them to the new user when the user is created (ie logs in)
How the query the fields afterwards (ie User.myBooleanField?)
Thanks!
Create a model called CustomUser or UserProfile, whatever you want, with these fields.
In settings.py add a setting AUTH_PROFILE_MODULE = "account.UserProfile", with what you named your model.
In the signals for social_auth, make sure the user has a profile, and if not create it for them when the user is created.
Now anywhere in the site you can call user.get_profile() and you'll have access to these fields.