Can't add a choice form field on custom usercreateform - django

I've been trying to create a custom signup form using an extended user model in django. One of the custom fields I've been trying to add is a user type which can only be either "Employee" or "Admin".
My signUpForm class looks like this
from .models import EMPLOYEE_TYPE_CHOICES
class SignUpForm(UserCreationForm):
usertype = forms.CharField(
max_length=10,
choices=EMPLOYEE_TYPE_CHOICES,
)
userID = forms.CharField(label="User ID")
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2', 'userID', 'usertype')
EMPLOYEE_TYPE_CHOICES comes from my models.py which look like this
EMPLOYEE_TYPE_CHOICES = (
('admin', 'Admin'),
('employee', 'Employee'),
)
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
ADMIN = 'Admin'
EMPLOYEE = 'Employee'
EMPLOYEE_TYPE_CHOICES = (
(ADMIN, 'Admin'),
(EMPLOYEE, 'Employee'),
)
usertype = models.CharField(
max_length=10,
choices=EMPLOYEE_TYPE_CHOICES,
)
userID = models.CharField(
max_length=10,
)
When running the server, I receive the error
TypeError: init() got an unexpected keyword argument 'choices'
Is there a different method for adding the choices to my form field?

The source of your error is due to using CharField instead of using ChoiceField. https://docs.djangoproject.com/en/1.11/ref/forms/fields/#choicefield
Even though what are you trying to accomplish wont work with the form created. You have to create a ModelForm for your Profile model. Then you can render the field simply on a template.

Related

Is it possible to add more fields to admin/auth/user?

models.py
class UserProfile(User):
bio = models.TextField(blank=True, null=True)
pfp = models.ImageField(verbose_name='Profile Picture', blank=True, null=True, upload_to="images/profile")
forms.py
class UserRegisterForm(UserCreationForm):
email = forms.EmailField(widget=forms.EmailInput(attrs={"class":"form-control", "placeholder":"example#example.com"}))
first_name = forms.CharField(widget=forms.TextInput(attrs={"class":"form-control"}))
last_name = forms.CharField(widget=forms.TextInput(attrs={"class":"form-control"}))
pfp = forms.ImageField(required=False, widget=forms.FileInput(attrs={"class":"form-control"}))
bio = forms.CharField(required=False, widget=forms.Textarea(attrs={"class":"form-control", 'rows':5, "placeholder":"Write something about yourself..."}))
class Meta:
model = UserProfile
fields = ['first_name', 'last_name', 'username', 'email', "pfp", "bio"]
When creating the user, the two newly added fields (bio and pfp) are not being saved in admin/auth/user, so my question is, is it possible to add those fields to the admin users database?
views.py
class SignUpView(CreateView):
form_class = UserRegisterForm
template_name = "registration/signup.html"
success_url = reverse_lazy("login")
are not being saved in admin/auth/user
Indeed, these are safed on the UserProfile model.
You thus can make a ModelAdmin for this:
# app_name/admin.py
from django.contrib import admin
from app_name.models import UserProfile
#admin.register(UserProfile)
class AuthorAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'username', 'email', 'pfp', 'bio')
Then the details can be seen in the admin/app_name/userprofile section.
Create a custom user model by inheriting the AbstractUser and adding extra fields needed. After that, register that custom user model by assigning it to AUTH_USER_MODEL.
Check here for detailed implementation.

Django Custom User is not showing a extra field with foreign key as Select list

I have a Custom User
class CustomUser(AbstractUser):
registration_code = models.ForeignKey(RegistrationCode, null=True)
...
def __str__(self):
return self.username
class RegistrationCode(models.Model):
code = models.CharField(max_length=30, null=True, blank=True)
def __str__(self):
return self.code
Within the admin page I would like to be able to set the "registration_code".
class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
list_display = ['email', 'username', 'registration_code']
fieldsets = (
(None, {
'fields': ('email', 'username', 'registration_code')
}),
)
admin.site.register(CustomUser, CustomUserAdmin)
But on the admin page I do not get a select list. But a default input field:
And if I edit the registration_code and try to save I get:
Cannot assign "'2'": "CustomUser.registration_code" must be a "RegistrationCode" instance.
Which sounds logical because I need to enter a RegistrationCode instance.
The same construction works for other models but for a Custom User it is not?? This is driving me crazy. Anybody an idea why I do not get the Select list?
Django handle foreign key by model instance not assignment. So the problem come from your CustomUserChangeForm where registratio_code must be ModelChoiceField

Displaying ManyToManyField correctly in DjangoAdmin

I'm trying to add a Likes model to a Posts application so that it records every time a User 'likes' a determined post. I have already added the likes field to the Posts model as a ManyToManyField and have tried to register it to display in the Django Admin, more exactly in the Post detail, but the result of the code I have only displays a list of all users where it should only be a list of the users that have liked the corresponding post.
Here's the code for my Posts model:
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
# ForeignKey that relates the post to the user that published it and their corresponding profile.
user = models.ForeignKey(User, on_delete=models.PROTECT)
profile = models.ForeignKey('users.Profile', on_delete=models.PROTECT)
title = models.CharField(max_length=255)
photo = models.ImageField(upload_to='posts/pics')
description = models.TextField(max_length=2000, blank=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
# ManyToManyField that translates to the users that have liked the post
likes = models.ManyToManyField(User, blank=True, related_name='post_likes')
def __str__(self):
return '{} by {}'.format(self.title, self.user.username)
def get_likes(self):
return '\n'.join([l.likes for l in self.likes.all()])
Also this is the code for the admin.py file.
#admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = (
'pk',
'user',
'title',
'photo',
'created',
'modified',
'get_likes',
)
list_display_links = ('pk', 'title',)
list_editable = ('photo',)
search_fields = (
'post__user',
'post__title'
)
list_filter = (
'created',
'modified'
)
fieldsets = (
('Profile', {
'fields' : (
'user',
)
}),
('Post', {
'fields' : (
'title',
'photo',
'likes',
)
})
)
How can I get the Django admin to display the ManyToManyField correctly in the post detail (i.e. display the users that have actually liked the post instead of a list of all users)?

Representing the other side of a one-to-one relationship in a serializer

I have a one-to-one relationship set up between a User model, and a UserProfile model. The UserProfile model contains additional information about the user, and is created immediately after a User model saves. The OneToOne field is set on the UserProfile.
The serialized User data appears fine when I access any of the views concering the UserProfile, but I would also like the UserProfile data to appear on User views, and I was wondering how to specify this in the UserSerializer.
EDIT: I need the UserProfile data so that I can do an easy lookup on the UserProfile id to change information about the User. I will have the User id client side. Ideally, I'd like to look up the User with that id, find the UserProfile id (amongst other UserProfile data), so that I can then change the data for the UserProfile that is associated with the User.
Example: user 1 is authenticated. I gather some information following registration, so I call the view /api/users/1/, which tells me that the user profile with an id of 20 is associated with that user. I can then update that user profile with the new data. I can't always assume that user id 1 will be associated with user profile id 1.
EDIT 2: Solved this similar to Angela's answer like this:
class UserSerializer(serializers.ModelSerializer):
user_profile = UserProfileSerializer(source="userprofile")
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups"
)
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = (
"user",
"current_city",
"current_country"
)
models.py
class UserProfile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE
)
avatar = models.FileField(
upload_to="uploads",
blank=True
)
age = models.IntegerField(
null=True
)
created_at = models.DateTimeField(
auto_now_add=True
)
current_city = models.CharField(
max_length=255,
blank=True
)
current_country = models.CharField(
max_length=255,
blank=True
)
I think, you should use Two UserProfileSerializer for this purpose as,
#serializer.py
class UserProfileSerializer_Temp(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = (
"user",
"current_city",
"current_country"
)
class UserSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
user_profile = kwargs.pop('user_profile', True)
super().__init__(*args, **kwargs)
if user_profile is False:
self.fields.pop('user_profile', None)
user_profile = UserProfileSerializer_Temp(source='userprofile_set')
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
class UserProfileSerializer_New(UserProfileSerializer_Temp):
user = UserSerializer(user_profile=False)
#views.py
class UserProfileAPI(ModelViewSet):
serializer_class = UserProfileSerializer_New
queryset = UserProfile.objects.all()
class UserAPI(ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
What's the relevence of user_profile=False ?
It's act like a simple flag. If we do not pass something like that from UserProfileSerializer_New, it will serializer the UserProfile twice. See this Screenshot --- image
Cureent Response
1 . UserAPI
2 . UserProfileAPI
So to have the userprofile information available for viewing in the user serializer and also have user info in the user serializer, I would recommend you use the serializer source arguements as follows -
Recommended Approach
This is based on the assumption that when you update user profile, you dont want to update any info in the actual user model. You use the user endpoint to do that.
class UserProfileSerializer(serializers.ModelSerializer):
first_name = serializers.CharField(source='user.first_name',read_only=True)
last_name = serializers.CharField(source='user.second_name',read_only=True)
...... #(other user fields you want to include)
......
......
class Meta:
model = UserProfile
fields = (
"first_name",
"last_name",
"current_city",
"current_country",
......., # any other user fields
.......
)
class UserSerializer(serializers.ModelSerializer):
user_profile = UserProfileSerializer(partial=True, required=False)
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
This will give you a nested representation of the user_profile inside your user object, which will also have the source fields, but you can choose to ignore that.
Another approach
Otherwise if you really want nested structures in both the serializers, you can use different serializers, but not sure what your use case is for this.
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = (
"current_city",
"current_country",
......., # any other user_profile only fields
.......
)
class UserSerializer(serializers.ModelSerializer):
user_profile = UserProfileSerializer(partial=True, required=False)
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
class UserSerializerNew(serializers.ModelSerializer):
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups"
)
class UserProfileSerializerNew(serializers.ModelSerializer):
user = UserSerializerNew(read_only=True)
class Meta:
model = UserProfile
fields = (
"current_city",
"current_country",
"user"
)

Updates Records related with Foreign key In Django Rest

I want to update records that are related with foreign Keys or related by any means. I know how to update single model records but I am not able to do the same in case of related models.
My Models :
class UserProfile(models.Model):
user = models.OneToOneField(User)
subject = models.ManyToManyField('Subjects')
phone = models.CharField(max_length=20)
address = models.TextField()
def __unicode__(self):
return self.user.username
class Subjects(models.Model):
name = models.CharField(max_length=100)
code = models.IntegerField()
def __unicode__(self):
return self.name
My serializers :
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'id',
'first_name',
'username',
'email',
'is_active',
)
class SubjectSerializer(serializers.ModelSerializer):
class Meta:
model = Subjects
fields = (
'name',
'code',
)
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
subject = SubjectSerializer(many=True)
class Meta:
model = UserProfile
fields = (
'user',
'subject',
'phone',
'address',
)
Views :
class UserProfileList(viewsets.ModelViewSet):
serializer_class = UserProfileSerializer
queryset = UserProfile.objects.all()
urls
router.register(r'api/user/profile', UserProfileList)
I can see the first record as /api/user/profile/1/ But when I'll try to update record from Browsable Rest Api it gives me the error user with same username already exist.
Edit :
I want to update UserProfile Model using UserProfileSerializer . Simple create new records , Update existing one and delete.
You do have a constraint the username that makes it impossible to create again.
You need to remove it by altering validators UserSerializer.username.
Make sure you don't remove others constraints by printing the serializer:
>>> print(UserSerializer())
>>> UserSerializer():
id = IntegerField(label='ID', read_only=True)
first_name = CharField(allow_blank=True, max_length=30, required=False)
username = CharField(help_text='Required. 30 characters or fewer. Letters, digits and #/./+/-/_ only.', max_length=30, validators=[<django.core.validators.RegexValidator object>, <UniqueValidator(queryset=User.objects.all())>])
email = EmailField(allow_blank=True, label='Email address', max_length=254, required=False)
is_active = BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', label='Active', required=False)
As you can notice, we do have a UniqueValidator on the username as well as a RegexValidator
You'll need to alter the class:
from django.core import validators
from django.utils.translation import ugettext_lazy as _
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'id',
'first_name',
'username',
'email',
'is_active',
)
extra_kwargs = {
'username': {
'validators': [
validators.RegexValidator(
r'^[\w.#+-]+$',
_('Enter a valid username. This value may contain only '
'letters, numbers ' 'and #/./+/-/_ characters.')
),]
}
}
Note that the validator here is taken out of the Django definition of that field (https://github.com/django/django/blob/master/django/contrib/auth/models.py#L309)
Now if you print again the serializer you should see that the unique constraint is removed:
>>> print(UserSerializer())
>>> UserSerializer():
id = IntegerField(label='ID', read_only=True)
first_name = CharField(allow_blank=True, max_length=30, required=False)
username = CharField(help_text='Required. 30 characters or fewer. Letters, digits and #/./+/-/_ only.', max_length=30, validators=[<django.core.validators.RegexValidator object>])
email = EmailField(allow_blank=True, label='Email address', max_length=254, required=False)
is_active = BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', label='Active', required=False)