Django rest framework not updating extended user model - django

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.

Related

Serialise an extended User Model in Django

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

Django user profile style auth with Djoser

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

How to return data from a database for a logged-in user in Django Rest Framework?

Scenario:
I have two APIs currently. A RegisterAPI and a LoginAPI. The RegisterAPI registers a new user and a LoginAPI allows them to access their details only. I used JWT authentication as well.
I want to retrieve data from the database for a particular user who logs in using the Login API in DRF. My intention is to interact with the database but the problem is that I have two models in my serializer with a One-To-One relationship and I am confused about how to move forward with this issue. To get a clearer picture, view my code below:
models.py:
from django.db import models
from django.contrib.auth.models import User
class UserData(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE)
company = models.CharField(max_length=100, blank=True, null=True)
address = models.TextField()
views.py:
class RegisterAPI(APIView):
permission_classes = [AllowAny]
def post(self, request, format=None):
serializer = UserDataSerializer(data=request.data)
if serializer.is_valid():
content = {
'status': 'Thanks for registering'}
return Response(content, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class LoginAPI(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
user_id = request.user.id
queryset = User.objects.filter(user = request.user)
user_serializer = UserSerializer(queryset, many = True)
return Response(user_serializer.data)
serializers.py:
from rest_framework import serializers
from users.models import UserData
from django.contrib.auth.models import User
class UserDataSerializer(serializers.ModelSerializer):
class Meta:
model = UserData
fields = ('company', 'address')
class UserSerializer(serializers.ModelSerializer):
info = UserDataSerializer()
class Meta:
model = User
fields = ('username', 'email', 'password', 'info')
def create(self, validated_data):
p_data = validated_data['info']
password = validated_data['password', None]
user = User.objects.create(**validated_data)
if password is not None:
user.set_password(password)
user.save()
UserData.objects.create(user = user, **p_data)
return validated_data
urls.py:
from rest_framework.routers import DefaultRouter
from users.views import LoginAPI, RegisterAPI
router = DefaultRouter()
router.register('login_api', LoginAPI, basename = 'login_api')
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(router.urls)),
path('register_api/', RegisterAPI.as_view(), name = 'register_api'),
]
I want to return the user's Username, Email, Company and Address within the LoginAPI. These are from both the User and Profile models.
Example:
{'username' = 'Tim',
'email' = 'tim#company.com',
'company' = 'Riko',
'address' = 'Kenya'}
However, I am not able to retrieve the records from the User model and Profile model when accessing the Login API URL.
I am getting this error:
AttributeError at /login_api/
Got AttributeError when attempting to get a value for field `info` on serializer `UserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'info'.**
Can anyone explain to me what went wrong?
DRF serializers subclasses Field (serializers.Field), so you can use the source argument to refer the attribute which should be used to resolve the value of the field (UserDataSerializer in this case):
class UserSerializer(serializers.ModelSerializer):
info = UserDataSerializer(source='userdata')
# ^^^^ Here
...
The error you're getting is pretty self-explanatory. You're passsing a User instance to the UserSerializer but you have a field defined as info. As the User instance does not have such attribute, it shows the error. So you need some way to tell the serializer the value of the field, using source to refer an existing attribite is one way of doing that.

Initialize user django so he has default settings values

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.

Can't POST after extending User model fields in Django REST

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"
}
}