Django "fields" attribute of user forms (UserCreationForm and UserChangeForm) - python

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?

Related

Add fields from another model to the admin site

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

Not all fields are showing correctly in django admin

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

Django restframework: How serialize two models queryset?

I have the following situation: A user can have more than one profile. Here is my models class. Something like this example:
models.py:
class Profile:
name=models.Charfield()
class UserProfile:
user=models.ForeignKey(User)
profile = models.ForeignKey(Profile)
serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
Here I'm returning all my users by JSON but I would like to add a new field called profiles that returns all ids profiles that the user have.
{ "id": 1,
"name" : "John"
....
profiles = [1, 2]
}
How can I get(query) all profiles that the user have and add them on my final JSON?
on the UserSerializer declare the field profile as PrimaryKeyRelatedField
profiles = serializers.PrimaryKeyRelatedField()
By default this field is read-write, although you can change this behavior using the read_only flag.
see docs
1) Simplify relations - only User and Profile models needed in this case, you don't have to store explicit relation in UserProfile table as the same could be done in Profile model, as long as you don't need Users to share profiles with other Users.
2) Create ProfileSerializer
3) Add profiles field to UserSerializer with many=True property & provide reference in 'source' property
Please reffer to these docs as they are really good
http://www.django-rest-framework.org/api-guide/serializers/
Another thing to mention, creating UserProfile is depricated and in new versions of Django you can extend basic User model using setting property AUTH_USER_MODEL

How to update a user profile field using rest API

I am new to django and very confused. I am using django as the backend API for my angular application.
I want to add few more details to the User model so I added the following to my models.py
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User)
company_name = models.CharField(max_length=100)
I am using an application to add rest authentication support: https://github.com/Tivix/django-rest-auth
Using this application I can edit a users profile with this URL doing a POST request: http://localhost:8080/rest-auth/user/
Question
How can I update the custom field company_name? while editing a users profile?
What I've tried
I tried to override the UserDetailsSerializer that the application provides but it isn't having any effect.
This is what I tried adding to my applications serializers.py
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserProfile
fields = ('company_name',)
class UserDetailsSerializer(serializers.ModelSerializer):
profile = UserProfileSerializer(required=True)
class Meta:
model = models.User
fields = ('id', 'username', 'first_name', 'last_name', 'profile')
If you are using django rest framework, then you basically want to have an update method on your view class for UserProfile that takes in the user id as a request param. Then in that method you want to use the ORM to get a model object for the given userprofile id, set the property that was also passed as a param, and save the changed model object. Then generate a success response and return it.
You can read more about how to do this here: http://www.django-rest-framework.org/api-guide/views/

Custom user model in django does not allow setting password in admin

I have created a custom user model which I am successfully using within my app.
The problem is that within the Admin, on the user edit screen, I get a display of the present password hash, instead of the very useful interface for setting the password.
I am using Django 1.5b1 on Python 2.7.
How can I convince the Django admin to treat my user model the same way it treats the native User, for the sake of the admin user interface?
Documentation suggest that you need to register the custom model with admin and also define few methods as well so that admin interface works with custom user model.
You may also have to define built-in forms for User.
From Custom users and django.contrib.admin
You will also need to register your custom User model with the admin. If your custom User model extends AbstractUser, you can use Django's existing UserAdmin class. However, if your User model extends AbstractBaseUser, you'll need to define a custom ModelAdmin class.
Just add this to your form:
password = ReadOnlyPasswordHashField(
label= ("Password"),
help_text= ("Raw passwords are not stored, so there is no way to see "
"this user's password, but you can change the password "
"using this form."))
Borrowed from https://stackoverflow.com/a/15630360/780262
At least in 1.10, I was able to do the following. Simpler than the above. I put it in admin.py:
from django.contrib.auth.admin import UserAdmin
from django.contrib import admin
from myapp.models import MyUser
class MyUserAdmin(UserAdmin):
model = MyUser
fieldsets = UserAdmin.fieldsets + (
(None, {'fields': ('a_custom_field',)}),
)
admin.site.register(MyUser, MyUserAdmin)
If you're extending AbstractBaseUser, you'll need two forms, a creation form, and an update form without the passwords, derived from ModelForm.
Then in the Admin form, specify separate create and update forms, along with corresponding fieldsets:
class MyUserAdmin(UserAdmin):
# The forms to add and change user instances
form = UserChangeForm
add_form = UserCreationForm
fieldsets = (
(None, {'fields': ('email', 'first_name', 'last_name')}),
)
add_fieldsets = (
(None, {'fields': ('email', 'password', 'password2', 'first_name', 'last_name')}),
)
Passwords for new users will work. Do password changes in the pre-defined django form instead of in admin.

Categories