Display field from another model in ModelAdmin of User model - django

My Django project is based on built-in User model.
For some extra attributes, I have defined another model:
models.py:
class Status(models.Model):
email = models.ForeignKey(User, on_delete=models.CASCADE)
is_verified = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
def __str__(self):
return self.email
And here's the custom ModelAdmin:
admin.py:
class CustomUserAdmin(UserAdmin):
model = User
list_display = ['email']
search_fields = ['email']
I want list_display to show me is_verified and is_active fields from Status model but I'm lost.
I've looked into some similar questions on SO like Display field from another model in django admin or Add field from another model Django but none of the solutions are applicable because one of my models is Django's built-in.

Try this.
models.py
class Status(models.Model):
# email = models.ForeignKey(User, on_delete=models.CASCADE)
email = models.OneToOneField(
User,
on_delete=models.CASCADE,
related_name='status'
)
is_verified = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
def __str__(self):
return self.email
admin.py
class CustomUserAdmin(UserAdmin):
model = User
list_display = ['email', 'is_verified', 'is_active']
search_fields = ['email']
def is_verified(self, obj):
return obj.status.is_verified
def is_active(self, obj):
return obj.status.is_active
If you want to apply the displayed name or ordering, please refer to the following
source
I saw your comments and tried running the code myself. There were some problems, so I modified admin.py as follows.
from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
admin.site.unregister(User)
#admin.register(User)
class CustomUserAdmin(UserAdmin):
list_display = ['email', 'status_is_verified', 'status_is_active']
list_filter = ['status__is_verified', 'status__is_active']
search_fields = ['email']
#admin.display(ordering='status__is_verified')
def status_is_verified(self, obj):
return obj.status.is_verified
#admin.display(ordering='status__is_active')
def status_is_active(self, obj):
return obj.status.is_active

Related

Adding fields from a many-to-many relationship to a Django Admin Inline

I am trying to set up a project where I have user, questions for the users, and then the answers that the users give. Here are my models.
class User(models.Model):
name = models.CharField(max_length=20)
email = models.EmailField()
def __str__(self):
return self.name
class Question(models.Model):
question = models.TextField()
def __str__(self):
return self.question
class Answer(models.Model):
answer = models.TextField()
user = models.ManyToManyField(User)
question = models.ManyToManyField(Question)
def __str__(self):
return self.answer
Then in the admin I would like to be able to click on a user and see their answers so I set my admin up like so.
class AnswerInline(admin.TabularInline):
model = Answer.user.through
class UserAdmin(admin.ModelAdmin):
list_display = ('name', 'email')
inlines = [AnswerInline]
class QuestionAdmin(admin.ModelAdmin):
list_display = ('question',)
class AnswerAdmin(admin.ModelAdmin):
list_display = ('answer', )
fields = ('question', 'answer',)
admin.site.register(User, UserAdmin)
admin.site.register(Question, QuestionAdmin)
admin.site.register(Answer, AnswerAdmin)
However, when viewing users it only shows their answer. It doesn't show the question that answer is associated with. How do I add this information to the admin page in a way that makes sense. Preferably with the question first and then the answer to that question to the right of it.
Update
I added this to the admin inline, however it shows up blank. So I still need it to show the actual string that is the "question"
class AnswerInline(admin.TabularInline):
model = Answer.user.through
fields = ('question', 'answer')
readonly_fields = ('question',)
extra = 0
def question(self, obj):
q = self.question.question
return q
So it turns out I set up the models incorrectly. Here is how they should look.
from django.db import models
class User(models.Model):
name = models.CharField(max_length=20)
email = models.EmailField()
def __str__(self):
return self.name
class Question(models.Model):
question = models.TextField(default=None)
people = models.ManyToManyField(User, through='Answer')
def __str__(self):
return self.question
class Answer(models.Model):
answer = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
question = models.ForeignKey(Question, on_delete=models.CASCADE, default=None)
def __str__(self):
return self.answer
and then here is the admin that works.
class AnswerInline(admin.TabularInline):
model = Answer
fields = ('question', 'answer')
readonly_fields = ('question', 'answer')
extra = 0
class UserAdmin(admin.ModelAdmin):
list_display = ('name', 'email')
inlines = [AnswerInline]
class QuestionAdmin(admin.ModelAdmin):
list_display = ('question',)
class AnswerAdmin(admin.ModelAdmin):
list_display = ('answer',)
fields = ('question', 'answer', 'user')

Extended the User model, but not quite sure how to serialize fields for both User and UserExtended

I extended my User model with a new model just called UserExtended:
# Django imports
from django.db import models
from django.contrib.auth.models import User
class UserExtended(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
crm_guid = models.UUIDField(unique=True)
security_q1 = models.CharField(max_length=255, blank=True, null=True)
security_a1 = models.CharField(max_length=255, blank=True, null=True)
security_q2 = models.CharField(max_length=255, blank=True, null=True)
security_a2 = models.CharField(max_length=255, blank=True, null=True)
attempts = models.SmallIntegerField(blank=False, null=False, default=0)
key = models.CharField(max_length=255, blank=True, null=True)
key_expires = models.DateTimeField(blank=True, null=True)
method = models.CharField(max_length=4, blank=True, null=True)
class Meta:
db_table = 'auth_user_extended'
I was hoping by just doing that some Django magic would take care of the rest and I wouldn't have to change my views.py or serializers.py. But when I send a request to the end-point I get:
[api] django.core.exceptions.ImproperlyConfigured: Field name `guid` is not valid for model `User`.
So it does apparently need to be specified. I've been looking at the documentation and similar SO questions to find an answer.
This is what I have for my views.py:
# Django imports
from django.contrib.auth.models import User
# Third party imports
from rest_framework import generics
from rest_framework.permissions import IsAdminUser
# App imports
from users.serializers import UserSerializer
class UsersListCreateView(generics.ListCreateAPIView):
permission_classes = [IsAdminUser]
serializer_class = UserSerializer
def get_queryset(self):
queryset = User.objects.all()
email = self.request.query_params.get('email')
username = self.request.query_params.get('username')
if email:
queryset = queryset.filter(email=email)
if username:
queryset = queryset.filter(username=username)
return queryset
class UserRetrieveUpdateDeleteView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsAdminUser]
queryset = User.objects.all()
serializer_class = UserSerializer
For my serializers.py I just have:
# Django imports
from django.contrib.auth.models import User
from users.models import UserExtended
from django.contrib.auth.hashers import make_password
# Third party imports
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'last_login', 'first_name',
'last_name', 'username', 'email', 'is_active', 'guid']
If I change model = User to model = UserExtemded, then I'll get an error like:
[api] django.core.exceptions.ImproperlyConfigured: Field name `last_login` is not valid for model `UserExtended`.
I'm thinking I need to do one of two things:
Create a serializer class for both models and call them both from the views.py. I've toyed with this a little by trying to pass a list or tuple in of serializer_class (apparently singular for a reason).
Setup the relationship in the serializers.py. I'm looking into this now.
Suggestions for how to resolve this issue?
You need a different serializer and viewset to operate on UserExtended
My suggestion would be keep old serializer as is and create UserExtendedSerializer
class UserExtendedSerializer(serializers.ModelSerializer):
user = UserSerializer(many=False, read_only=True)
class Meta:
model = UserExtended
fields = "__all__"
and viewset would be simply:
class UserExtendedViewSet(ModelViewSet):
serializer_class = UserExtendedSerializer
queryset = UserExtended.objects.all()
this should solve your issue

Get username in model with extended user

At the moment, I get the user id when I create a new article. I would like also to have the username in the model (I'm using DRF with React, so it would save me a get request just for the username).
models.py:
class Article(models.Model):
title = models.CharField(max_length=100)
user = models.ForeignKey(
User, related_name='Article', on_delete=models.CASCADE)
serializers.py:
class ArticleSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = Article
fields = ['id', 'title', 'user']
def save(self, **kwargs):
kwargs["user"] = self.fields["user"].get_default()
return super().save(**kwargs)
The code below should get the username attribute from the user field.
getattr(self.fields['user'], self.fields['user'].USERNAME_FIELD)
Here is the solution:
class ArticleSerializer(serializers.ModelSerializer):
user = PublicProfileSerializer(
read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = Article
fields = ['id', 'title', 'user']
def save(self, **kwargs):
kwargs["user"] = self.fields["user"].get_default()
return super().save(**kwargs)
with PublicProfileSerializer:
from django.contrib.auth import get_user_model
UserModel = get_user_model()
class PublicProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserModel
fields = [
"id",
"first_name",
"last_name",
"username",
]

Django Rest Framework link logged user to app model

When I add a new Article, I have a dropdown with a list of all registered users. I want to link the current user to the new Article.
models.py
from django.db import models
from django.conf import settings
User = settings.AUTH_USER_MODEL
class Article(models.Model):
title = models.CharField(max_length=100)
user = models.OneToOneField(User, related_name='Article', on_delete=models.CASCADE)
def __str__(self):
return self.title
serializers.py
from rest_framework import serializers
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'title', 'user']
Here is the solution:
class ArticleSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = Article
fields = ['id', 'title', 'user']
def save(self, **kwargs):
kwargs["user"] = self.fields["user"].get_default()
return super().save(**kwargs)

Add foreign key field and choices charfield to Django user creation form

I would like to modify Django UserCreationForm so that it would support creating my custom user. There are required fields company and role which both should offer some kind of selection to pick the correct choice (there will be only 3 roles but there can be hundreds of companies).
I believe I need to extend UserCreationForm and modify UserAdmin. However, I have followed several different examples but so far in vain. Below is the model. How can I command Django to add the extra fields to the user creation form?
ROLE_CHOICES = [
('role1', 'Role 1'),
('role1', 'Role 2'),
('janitor', 'Janitor'),
]
class Company(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100)
class Meta:
ordering = ('created',)
db_table = "company"
def __str__(self):
return self.name
class CustomUser(AbstractUser):
created = models.DateTimeField(auto_now_add=True)
username = models.CharField(max_length=100, unique=True)
email = models.EmailField(max_length=200, unique=True)
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='%(class)s_company')
role = models.CharField(
max_length=100,
choices=ROLE_CHOICES,
default='janitor',
)
phone_number = models.CharField(null=True, blank=True, max_length=20)
class Meta:
ordering = ('created',)
db_table = "custom_user"
def __str__(self):
return self.username
You don't have to extend UserCreationForm. just use this:
forms.py
from django import forms
from .models import CustomUser
class UserRegistrationForm(forms.ModelForm): # you can name it anything
class Meta:
model = CustomUser
fields = ('username', 'email', 'company',....) # add the fields here you want in form just not created. it's auto fill
Use this form.
If you want admin. Write this in admins.py
from .models import CustomUser
class CustomUserAdmin(admin.ModelAdmin):
list_display = ('username', 'email', 'company'...) # add fields as you want
admin.site.register(CustomUser, CustomUserAdmin)
Hope this help. If not, please comment.