I'm trying to implement authentication with djoser. Since I haven't extended my AbstractBaseUser from Django at start of my project I decided to use a one-to-one profile relation pattern. I have a seperated user app and Here are my related codes:
# models.py (user and profile models)
from django.db import models
# Create your models here.
from django.db import models
from django.contrib.auth.models import User as BaseUser
class User(BaseUser):
#property
def mobile_number(self):
return self.profile.mobile_number
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
mobile_number = models.CharField(max_length=11, unique=True)
def __str__(self):
return self.user.username
# settings.py (djoser settings)
...
DJOSER = {
"SERIALIZERS":{
"user": "user.serializers.UserSerializer"
"current_user": "user.serializers.UserSerializer"
"user_create": "user.serializers.UserCreateSerializer"
}
...
}
...
# user.serializers.py
from rest_framework import serializers
from .models import UserProfile
from .models import User
from djoser.serializers import UserCreateSerializer as BaseUserCreateSerializer
from djoser.serializers import UserSerializer as BaseUserSerializer
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
exclude = ["user"]
class UserSerializer(serializers.ModelSerializer):
mobile_number = serializers.CharField(required=True, allow_blank=False)
class Meta:
model = User
fields = ["username", "email", "first_name", "last_name", "mobile_number"]
class UserCreateSerializer(BaseUserCreateSerializer):
mobile_number = serializers.CharField(required=True, allow_blank=False)
class Meta(BaseUserCreateSerializer.Meta):
fields = ["username", "email", "first_name", "last_name", "mobile_number"]
def create(self, validated_data):
mobile_number = validated_data.pop("mobile_number")
user = super.create(**validated_data)
UserProfile.objects.create(user=user, mobile_number=mobile_number)
return user
What I'm trying to do here is
Skip migrations with adding just a property to django User model.
Extending Djoser serializer to just create the relation in create step.
But when i try to create user with post request to djoser endpoint (auth/users/create) with body like this:
{
"username": "user",
"email": "user#examssple.com",
"first_name": "hatef",
"last_name": "madani",
"mobile_number": "01236549879"
}
I receive this error: "User() got an unexpected keyword argument 'mobile_number'". Any help on current approach or better one would be great.
I think you should follow this official documentation: https://docs.djangoproject.com/en/3.1/topics/auth/customizing/
your custom User should extends AbstractBaseUser or AbstractUser and you need customize a UserManager with create_user(..) method
Related
I am trying to extend the User model to allow a user to add an image to their profile, however have no option to save an image in the admin for my page. The other extended fields are showing. I am not sure why this is..
model.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.TextField(default='', blank='')
last_name = models.TextField(default='', blank='')
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
def user_image_upload_handler(instance, filename):
fpath = pathlib.Path(filename)
new_fname = str(uuid.uuid1()) # uuid1 -> uuid + timestamps
return f"request/{new_fname}{fpath.suffix}"
class ProfilePicture(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(upload_to=user_image_upload_handler, blank=True)
admin.py
from django.contrib import admin
from django.contrib.auth.models import User
from .models import Profile, ProfilePicture
class ProfileAdmin(admin.ModelAdmin):
model = [Profile]
list_display = ['id']
class ProfilePictureAdmin(admin.ModelAdmin):
model = [ProfilePicture]
extra = 10
class UserAdmin(admin.ModelAdmin):
model = ProfileAdmin
model = ProfilePictureAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
from https://docs.djangoproject.com/en/4.0/ref/contrib/auth/
first_name, last_name and email are already part of the standard django User so there was no need to extend those. My accounts/admin.py was simply
from django.contrib import admin
from django.contrib.auth.models import User
from .models import ProfilePicture
class ProfilePictureAdmin(admin.StackedInline):
model = ProfilePicture
extra = 1
class ExtendedUserAdmin(admin.ModelAdmin):
inlines = [ProfilePictureAdmin]
list_display = ['id', 'username', 'first_name']
admin.site.unregister(User)
admin.site.register(User, ExtendedUserAdmin)
I'm extending a django auth user model in a Profile model:
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
activity = models.IntegerField(default=500)
def _str_(self):
return self
in my views I'm getting the current auth user and I get the associated profile:
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def getUserProfile(request):
profile = Profile.objects.get(user = request.user)
serializer = profileSerializer(profile, many=False)
return Response(serializer.data)
Here is my serializers code:
from rest_framework import serializers
from .models import Profile
class profileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('first_name', 'activity')
The error I'm getting that Profie object has not a first_name attribute, but when I replace 'first_name' with 'user' I get only the id of the user, so I want to show the first_name as well.
Thank you
The OneToOneField connects your field user in the Profile model to a User object which has a first_name attribute, but in the Profile table that user field is just a number that says which User object it is. To access the first name, you can do this:
from rest_framework import serializers
from .models import Profile
class profileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = '__all__'
Then in your template you could display the first_name like this, assuming you pass person to it where person is an instance of the Profile model:
{{ person.user.first_name }}
I also think it would be less confusing to use another name for the field, rather then user. Call it person maybe, like:
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
person = models.OneToOneField(User, on_delete=models.CASCADE)
activity = models.IntegerField(default=500)
def _str_(self):
return self
Try this:
from django.contrib.auth.models import User
class profileSerializer(serializers.ModelSerializer):
first_name = serializers.CharField(max_length=200, read_only=True)
class Meta:
model = Profile
fields = ('user', 'first_name', 'activity')
def get_first_name(self, obj):
return User.objects.filter(id=obj.user.id).first().values_list('first_name', flat=True).last()
I was able to show the fields I wanted using Django depth in the serializer
from rest_framework import serializers
from .models import Profile
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('first_name', 'last_name')
class profileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Profile
fields = ('activity','user')
depth=1
I am using Django + Angular 2
. I am creating a user fine but he does not have the default values i want...
Model
class Settings(models.Model):
user = models.ForeignKey('auth.User', related_name='settings', on_delete=models.CASCADE)
bolean1 = models.BooleanField(default=False)
boolean2 = models.BooleanField(default=False)
boolean3 = models.BooleanField(default=False)
string1 = models.CharField(max_length=100, default='No description')
Serializer
class SettingsSerializer(serializers.ModelSerializer):
class Meta:
model = Settings
fields = ('id', 'bolean1', 'bolean1', 'bolean3', 'string1')
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password' ,'settings', 'image')
extra_kwargs = {'password': {'write_only': True}}
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'username', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User(
email=validated_data['email'],
username=validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
Views
class SettingsValues(generics.ListAPIView):
serializer_class = SettingsSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get_queryset(self):
queryset = Settings.objects.all()
queryset = queryset.filter(user=self.request.user.id)
return queryset
class RegisterUser(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = CreateUserSerializer
The problem is that when i create a new user he does not have default values,e.g boolean 1, boolean 2 etc. i must go to django admin create new setting and choose the user.
Any idea ?
Sounds like you want Django's Signals, specifically post_save. You'll want to create a Settings() for each User() that's created - you have default values right now, but that only helps when you've saved the Settings model.
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
User = get_user_model()
#receiver(post_save, sender=User)
def create_settings(sender, instance, created, **kwargs):
# Only create a Settings relation if the User is new
if created:
settings = Settings(user=instance)
settings.save()
If your Settings model is in an app called settings, then your settings/apps.py should have this - good StackOverflow answer on this:
from django.apps import AppConfig
class SettingsConfig(AppConfig):
name = 'settings'
def ready(self):
from . import signals
Now, whenever a new User is created, a Settings relation is created for them as well.
I am not sure if this is what you want to achieve, but try this.
//admin.py
from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
from accounts import models
admin.site.unregister(User)
class UserRoleInline(admin.StackedInline):
model = models.UserRole
class UserRoleAdmin(UserAdmin):
inlines = [UserRoleInline]
admin.site.register(User, UserRoleAdmin)
//model.py
from django.db import models
from django.contrib.auth.models import User
class RoleType(object):
MANAGER = 'm'
CANDIDATE = 'c'
CHOICES = ((MANAGER, 'Manager'), (CANDIDATE, 'Candidate'))
class UserRole(models.Model):
user = models.OneToOneField(User)
role = models.CharField(max_length=1, choices=RoleType.CHOICES)
Reference: django StackedInline
This will help you stack the setting models at end of User model. This won't automatically take a default value, but will let you choose the settings as the user models is created.
I have extended user model in Django, and it is of Customer type. I am using django rest framework (DRF). So, while going through docs in DRF I came to know about writing in nested models, so I did override create and update methods in serializer, creation is working fine but not the update as it says :
HTTP 400 Bad Request
Allow: GET, PUT, PATCH, DELETE, OPTIONS
Content-Type: application/json
Vary: Accept
{
"user": {
"username": [
"A user with that username already exists."
]
}
}
Here is my Customer model:
from django.db import models
from django.contrib.auth.models import User
class Customer(models.Model):
user = models.OneToOneField(User, related_name="customer", on_delete=models.CASCADE)
date_of_birth = models.DateField(max_length=8)
def __unicode__(self):
return u'%s' % self.user.username
My User serializer:
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'is_staff')
And my extended customer model serializer:
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from rest_framework import serializers
from customers.models import Customer
from api.serializers import UserSerializer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Customer
fields = ('url', 'date_of_birth', 'user')
depth = 1
def create(self, validated_data):
print "coming inside create"
user_data = validated_data.pop("user")
user = User.objects.create(**user_data)
customer = Customer.objects.create(user=user, **validated_data)
return customer
def update(self, instance, validated_data):
print "coming inside update"
user_data = validated_data.pop("user")
username = user_data.pop('username')
user = get_user_model().objects.get_or_create(username=username)[0]
user.email = user_data.get('email', user.email)
user.save()
instance.user = user
instance.date_of_birth = validated_data.get('date_of_birth', instance.date_of_birth)
instance.save()
return instance
Here is the viewset view:
from rest_framework import viewsets
from customers.models import Customer
from customers.serializers import CustomerSerializer
class CustomerViewSet(viewsets.ModelViewSet):
serializer_class = CustomerSerializer
queryset = Customer.objects.all()
So, what could be wrong here that it is creating a new profile of customer even the new user but not updating?
Edit 1
Ok, so I did this on UserSerializer:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'is_staff')
extra_kwargs = {
'username': {'validators': []},
}
So, update is fine for all the other fields of user or customer fields but
if I try to set a new username it creates an entirely new user,
and also when trying to create a new user, it gives the following error:
IntegrityError at /api/customers/
duplicate key value violates unique constraint "auth_user_username_key"
DETAIL: Key (username)=(customer1) already exists.
You should drop the unique validator for the nested serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'is_staff')
extra_kwargs = {
'username': {'validators': []},
}
You may want to print your serializer before to make sure you don't have other validators on that field. If you have some, you'll have to include them in the list.
I am able to extend User model fields and I can see API browser with these fields and inputs, but when I try to POST a new user I got this error:
'signup' is an invalid keyword argument for this function
or if I change in serializers.py
User.objects.create(**validated_data)
to
SignUp.objects.create(**validated_data)
I got
'username' is an invalid keyword argument for this function
serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
city = serializers.CharField(source='signup.city')
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'city')
def create(self, validated_data):
return User.objects.create(**validated_data)
models.py
from django.contrib.gis.db import models
from django.contrib.auth.models import User
class SignUp(models.Model):
user=models.OneToOneField(User, primary_key=True)
city = models.CharField(max_length=50, blank=True)
def __unicode__(self):
return self.first_name
You'll want to do this through the related_name field in your model and then you can reference it on the serializer.
class SignUp(models.Model):
user=models.OneToOneField(User, primary_key=True, related_name='signup') # <---
I don't think you can flatten nested fields into the parent model's serializer.
You can do a nested field:
class UserSerializer(serializers.ModelSerializer):
city = serializers.CharField(source='signup')
If you access it via the JSON path, you'll get an object like:
{
url: "localhost:8000/users/1",
username: "foo",
email: "foo#example.com",
signup: {
city: "Seattle"
}
}