My Profile model has a OneToOne relation with Django's built-in User model.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
verified = models.BooleanField(default=False)
If I want to change user's password or properties like Active or Superuser I have to do it in one Change User page, and to edit verified property I have to go to another.
Is there any way to merge this:
And this:
Into one form so I can edit everything about a user in one page?
Edit 1:
As you guys suggested the StackedInline approach, let's see how it turns out.
Please first look at Django's default Admin site (first screenshot above):
Everything is grouped in sections and sections have titles.
Look at how the password information is displayed.
There's a link to change the password.
Now I implement the StackedInline solution.
Please note that this is in the admin.py of my myapp:
from django.contrib import admin
from .models import Profile
from django.contrib.auth.models import User
# Register your models here.
class ProfileInline(admin.StackedInline):
model = Profile
class UserAdmin(admin.ModelAdmin):
inlines = (ProfileInline, )
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
Now let's look at Admin site:
Everything is scattered. Sections and their titles are gone (Personal info, Permissions, etc).
Password field shows the hashed password. All other information is gone.
There's no link to change the password.
Edit 2:
To solve the problem of Edit 1 I look at the source code of Django (https://github.com/django/django/blob/main/django/contrib/auth/admin.py) and add update my code as below:
class UserAdmin(admin.ModelAdmin):
inlines = (ProfileInline, )
fieldsets = (
(None, {"fields": ("username", "password")}),
(("Personal info"), {"fields": ("first_name", "last_name", "email")}),
(
("Permissions"),
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
"groups",
"user_permissions",
),
},
),
(("Important dates"), {"fields": ("last_login", "date_joined")}),
)
add_fieldsets = (
(
None,
{
"classes": ("wide",),
"fields": ("username", "password1", "password2"),
},
),
)
filter_horizontal = (
"groups",
"user_permissions",
)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
Now I have two sections in the Admin site:
The section on the top shows almost everything (except that the password field is still different and there's no link to change the password and also the verified field is not there) but sections and titles are back.
Then there's this additional and completely unnecessary part:
As you can see:
All fields of information about the user is repeated
Look at the password field
Information is not grouped in sections with titles
verified filed appears.
OP can use one of the InlineModelAdmin objects such as StackedInline. This allows one to create inline forms providing one the ability to edit models on the same page as a parent model.
Adapting for OP's case, it would be something like
from django.contrib import admin
class ProfileInline(admin.StackedInline):
model = Profile
class UserAdmin(admin.ModelAdmin):
inlines = [
ProfileInline,
]
admin.site.register(User, UserAdmin)
Now OP's admin site is set up to edit Profile objects inline from the User detail page.
User model Extension vs Inheritance.
Your profile model only add some elements to user. In this case Model inheritance can be better.
# models.py
class Profile(user):
verified = models.BooleanField(default=False)
after that you can achieve all fields for user and for profile:
# admin.py
class ProfileAdmin(ModelAdmin):
fields = '__all__'
if you don't want to switch on Model inheritance.
You can use InlineModel in UserAdmin to change related model.
# admin.py
class ProfileInline(StackedInline):
model=Profile
class UserAdmin(ModelAdmin):
inlines = (ProfileInline, )
you can use override AbstractUser from model Django so you can merge user in one place like this:
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
verified = models.BooleanField(default=False)
and you need to add this line to Django settings.py file so that Django knows to use the new User class:
AUTH_USER_MODEL = 'users.CustomUser'
then make sure to migrate :
(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
Related
According to Django docs:
It is strongly recommended that you explicitly set all fields that should be edited in the form using the fields attribute.
I have a custom user model, so I overrode UserCreationForm and UserChangeForm, but I'm not sure about the fields attribute of the Meta class.
The admin site will be editing all fields of a user; so in UserChangeForm, do I have to include all fields in this attribute? like this:
class Meta:
model = User
fields = (
"email",
"password",
"is_active",
"is_staff",
"is_superuser",
"date_joined",
"last_login",
"groups",
"user_permissions",
# maybe there are others that I'm missing?
)
Or in this case, it's safe to use the '__all__' shortcut?
The admin site uses UserChangeForm for editing user attributes (including permissions and so); so these need to be included in the fields attribute. But does this mean using the UserChangeForm anywhere other than the admin site, causes those security issues mentioned in the docs?
I've added a plan field to my custom Account class, but cannot get it to show on the individual account page in the django admin. It shows correctly in table of all accounts in the list view (as denoted by list_display), but does not show on each individual account page.
Here's my Account model:
class Account(AbstractUser):
PLANS = (
("free", "free"),
("pro", "pro")
)
plan = models.CharField(max_length=10, choices=PLANS, default="free")
def __str__(self) -> str:
return self.first_name
And my admin file:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from accounts.models import Account
from subscribers.models import Subscriber
class SubscriberInline(admin.TabularInline):
model = Subscriber
extra = 0
class AccountAdmin(UserAdmin):
inlines = [SubscriberInline, ]
list_display = ("first_name", "plan", "email")
# fields = ("first_name", "plan", "email")
admin.site.register(Account, AccountAdmin)
Why does this happen?
Is the problem related to the custom Account class which inherits from the AbstractUser model? I thought to add fields to the AccountAdmin class, but that just returns the below error:
ERRORS:
<class 'accounts.admin.AccountAdmin'>: (admin.E005) Both 'fieldsets' and 'fields' are specified.
The plan field also doesn't show in the admin panel when trying to create a new account (mind you, neither do most of the other fields as it only asks for the username, password1 and password2 fields, and the option to add new subscribers to the table, but other fields like first_name etc can be edited in the admin after creation).
Thanks
UPDATE:
Adding user #bdbd's suggested changes seems to not make a difference to the admin area - am I adding this incorrectly? Here's my admin.py after adding #bdbd's changes:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from accounts.models import Account
from subscribers.models import Subscriber
from django.contrib.auth.forms import UserChangeForm
from django import forms
PLANS = (
("free", "free"),
("pro", "pro")
)
class MyAccountChangeForm(UserChangeForm):
plan = forms.ChoiceField(choices=PLANS)
class SubscriberInline(admin.TabularInline):
model = Subscriber
extra = 0
class AccountAdmin(UserAdmin):
form = MyAccountChangeForm
inlines = [SubscriberInline, ]
list_display = ("first_name", "plan", "email")
# #todo: bug! plan field not showing in django admin on individual account pages
admin.site.register(Account, AccountAdmin)
UPDATE 2:
Screenshots of admin area:
You should not extend from UserAdmin. Instead, you should create your own model admin class which extends from admin.ModelAdmin.
Then you should register your model separately.
class AccountAdmin(admin.ModelAdmin):
...
admin.site.register(Account, AccountAdmin)
As necessary, you can customize AccountAdmin to get the effect you want. You can peek at the UserAdmin source code to see how it is customized, if you want your admin view to behave similarly.
You need to override the default form that is being used by UserAdmin and add your field like so:
from django.contrib.auth.forms import UserChangeForm
from django import forms
class MyAccountChangeForm(UserChangeForm):
plan = forms.ChoiceField(choices=PLANS)
Then assign the form to your admin:
class AccountAdmin(UserAdmin):
form = MyAccountChangeForm
I was trying to customize Django (version 1.8) admin page of Users (which is under Authentication and Authorization on admin page), but I cannot get it to work.
Currently, the default admin page of Users shows 5 defaults fields: username, email address, first name, last name, staff status. And, I am trying to make the admin Users page show following fields: username, email. date_joined, last_login
So, I created admin.py file and saved it in the directory where wsgi.py is with the following code:
class UserAdmin(admin.ModelAdmin):
list_display = ("username", "email", "date_joined", "last_login")
class Meta:
model = User
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
But, the code above did not force admin User page display the fields that I want it to display("username", "email", "date_joined", "last_login").
Then, I replaced the code above with the following code in admin.py:
class MyUserAdmin(UserAdmin):
list_display = ("username", "email", "date_joined", "last_login")
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)
But, no luck. There was a similar question on stackoverflow and answers to it (How to customize the auth.User Admin page in Django CRUD?), and above two blocks of code are what I was trying to do after reading the answers. But, I still cannot get it to work.
Can anyone help me?
I just found out what the problem was. The problem was that I did not include the app in INSTALLED_APPS in settings.
The Django admin panel has a search autocomplete with search_fields on the list of objects of a model, but right now I have 3000 users. To add a user manually is dificult with the selectbox; I need the same behavior like the searchfields for selecting a user foreinkey User.
How can I include the Django search feature on the form for editing inside the admin panel?
from myapp.models import Red
from django.contrib.auth.models import User
class Red(models.Model):
customer = models.ForeignKey(User, verbose_name="Cliente")
pub_date = models.DateTimeField(default=datetime.now, blank=True)
Since Django 2.0, you can use autocomplete_fields to generate autocomplete fields for foreign keys.
class UserAdmin(admin.ModelAdmin):
search_fields = ['username', 'email']
class RedAdmin(admin.ModelAdmin):
autocomplete_fields = ['customer']
Django has no built-in autocomplete functionality for foreign keys on admin but the raw_id_fields option may help:
class RedAdmin(admin.ModelAdmin):
raw_id_fields = ("customer", )
If you want real autocomplete then you have to use 3rd-party app like django-autocomplete-light or some of the other solutions.
I'm attempting to extend django's contrib.auth User model, using an inline 'Profile' model to include extra fields.
from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
class Profile(models.Model):
user = models.ForeignKey(User, unique=True, related_name='profile')
avatar = '/images/avatar.png'
nickname = 'Renz'
class UserProfileInline(admin.StackedInline):
model = Profile
class UserProfileAdmin(UserAdmin):
inlines = (UserProfileInline,)
admin.site.unregister(User)
admin.site.register(User, UserProfileAdmin)
This works just fine for the admin 'Change User' page, but I can't find a way to add inline model fields in the list_display. Just specifying the names of Profile fields in list_display give me an error:
UserProfileAdmin.list_display[4], 'avatar' is not a callable or an attribute of 'UserProfileAdmin' or found in the model 'User'.
I can create a callable which looks up the user in the Profile table and returns the relevant field, but this leaves me without the ability to sort the list view by the inline fields, which I really need to be able to do.
Any suggestions?
You've mentioned the only solution - creating a callable. There's currently no other way to do it, and yes this does mean you can't sort by that column.