User has no userprofile when using recommended method - django-rest-auth

I am adding a field to the user by using the recommend method here. #3 How can I update UserProfile assigned to User model?
When I attempt to save zip code on the /rest-auth/user/ url I get the exception "User has no userprofile."
model:
class UserProfile(models.Model):
user = models.OneToOneField(User)
# custom fields for user
zip_code = models.CharField(max_length=5)
serializer:
class UserSerializer(UserDetailsSerializer):
zip_code = serializers.CharField(source="userprofile.zip_code")
class Meta(UserDetailsSerializer.Meta):
fields = UserDetailsSerializer.Meta.fields + ('zip_code',)
def update(self, instance, validated_data):
profile_data = validated_data.pop('userprofile', {})
zip_code = profile_data.get('zip_code')
instance = super(UserSerializer, self).update(instance, validated_data)
# get and update user profile
profile = instance.userprofile
if profile_data and zip_code:
profile.zip_code = zip_code
profile.save()
return instance
Thank in advance!

Related

TypeError: Field 'id' expected a number but got <class 'rest_framework.fields.CurrentUserDefault'>

Seems I need to apply a dot notation to CurrentUserDefault() object, tried .id but failed
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self,obj):
queryset=TagPrivate.objects.filter(user=serializers.CurrentUserDefault) # <--TypeError
return TagPrivateSerializer(queryset).data
models.py
class DotPrivate(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
name = models.CharField(max_length=255)
description = models.TextField(max_length=350, blank=True)
lon = models.CharField(max_length=20)
lat = models.CharField(max_length=20)
rating = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(5.0)])
tag = models.ForeignKey('TagPrivate', on_delete=models.PROTECT)
in the following link in the first answer I found some solution but I do not completly understand it:
'CurrentUserDefault' object has no attribute 'user'
class TagPrivateSerializer(serializers.ModelSerializer):
class Meta:
model = TagPrivate
fields = ('id', 'name')
read_only_fields = ('id',)
You can not use CurrentUserDefault, this is just a value that the Django serializer will use for a default=… parameter, and then later swap for the request user.
You can fetch this from the request in the context, so:
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self, obj):
queryset = TagPrivate.objects.filter(user=self.context['request'].user)
return TagPrivateSerializer(queryset).data
In the ModelViewSet, you will need to pass the user, so:
class DotPrivateViewSet(ModelViewSet):
queryset = # …
permission_classes = # …
serializer_class = DotPrivateSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context.update(request=self.request)
return context

'TaskSerializer' object has no attribute 'start_date'

Hope You Are Good!
My question is how to get user input data in perform create method?
like in Django I use:
form.some_field
to get the field data but i can't do this in rest framework:
def perform_create(self, serializer):
if serializer.start_date > date.today():
serializer.status = "Schedule"
serializer.save(user=self.request.user)
I get this issue:
'TaskSerializer' object has no attribute 'start_date'
here is my model:
class Task(models.Model):
status_options = (("In Progress","In Progress",),("Cancelled","Cancelled",),("Completed","Completed",),("Schedule","Schedule",),)
priority_options = (("Low","Low",),("Medium","Medium",),("High","High",),)
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
description = models.TextField()
priority = models.CharField(max_length=124, choices=priority_options, default="Low")
status = models.CharField(max_length=124, choices=status_options, default="In Progress")
start_date = models.DateField()
end_date = models.DateField()
timestamp = models.DateField(auto_now_add=True)
altimedatetamp = models.DateTimeField(auto_now_add=True)
recurring = models.BigIntegerField(default=0)
....
You have to access serializer.validated_data to get your model field value.
And instead of manipulating the status value inside the serializer, consider passing it along the save method like you do with the user.
Your perform_create should look something like that:
def perform_create(self, serializer):
if serializer.validated_data['start_date'] > date.today():
serializer.save(user=self.request.user, status='Schedule')
else:
serializer.save(user=self.request.user)

Django REST foreign key issue

Trying to implement and test my own serializer, I have the following issue:
Searched for many question including this error message, I didn't manage to find any solution to my problem.
DRF Foreign key misusage?
ValueError: Cannot assign "'effcad53-bc45-41fa-be43-4f22c0376eb5'": "Product.related_workspace" must be a "Workspace" instance.
The Workspace class:
class Workspace(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
)
related_login = models.ForeignKey(
Login,
on_delete=models.PROTECT,
)
description = models.CharField(
max_length=150,
)
def __str__(self):
login = Login.objects.get(pk=self.related_login_id)
return f'{login.username} ({self.description})'
class Meta:
db_table = 'workspaces'
The Product class:
class Product(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
)
related_workspace = models.ForeignKey(
Workspace,
on_delete=models.PROTECT,
)
code = models.CharField(
max_length=50,
)
description = models.CharField(
max_length=150,
)
class Meta:
db_table = 'products'
unique_together = ('related_workspace', 'code',)
The ProductSerializer class:
class ProductSerializer(serializers.Serializer):
id = serializers.UUIDField(read_only=True)
related_workspace = serializers.UUIDField()
code = serializers.CharField()
description = serializers.CharField()
def create(self, validated_data):
return Product.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.related_workspace = validated_data.get('related_workspace', instance.related_workspace)
instance.code = validated_data.get('code', instance.code)
instance.description = validated_data.get('description', instance.description)
instance.save()
return instance
The script I'm using to test my serializer :
PROJECT_NAME = 'api'
def main():
#
from api.applications.product.models import Product
from api.applications.product.serializers import ProductSerializer
#
# Create a product
#
code = 'P001'
description = f'Product {code}'
#
# This is a valid workspace id!
#
related_workspace = 'effcad53-bc45-41fa-be43-4f22c0376eb5'
#
product = Product(
code=code,
description=description,
related_workspace=related_workspace,
)
product.save()
#
serializer = ProductSerializer(product)
print('serializer.data:', serializer.data)
if __name__ == '__main__':
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '%s.settings' % PROJECT_NAME)
import django
django.setup()
main()
Any advice of what I'm missing ?
First thing, when you are testing anything in Django its easiest to write test classes in a test module and use manage.py to run them. All that setup logic you are doing is already in manage.py.
python manage.py test my_app.tests.my_test_class
In your serializer you are setting a foreign key to related_workspace = serializers.UUIDField(), instead you want to nest your serializers (docs) so it pulls out the Workspace data, serializes, saves, and returns a Workspace object. You should have two serializers and they should look something like this, I am going to use ModelSerializer.
from rest_framework import serializers
# Workspace Serializer
class WorkspaceSerializer(serializers.ModelSerializer):
class Meta:
model = Workspace
fields = "__all__"
class ProductSerializer(serializers.ModelSerializer):
related_workspace = WorkspaceSerializer()
class Meta:
model = Product
fields = ["id", "related_workspace", "code", "description"]
def create(self, validated_data):
workspace_data = validated_data.pop('related_workspace')
workspace_serializer = WorkspaceSerializer(data=workspace_data)
if workspace_serializer.is_valid(reaise_exception=True):
workspace = workspace_serializer.save()
product = Product.objects.create(related_workspace = workspace, **validated_data)
return product
We want to remove the workspace data, then feed it to its own serializer, if that data is valid, we save the serializer and we get a Workspace object in return. Then we can create our Product object with that returned workspace object.
Searching deeper, my mistake was in the way I was creating my Product instance...
In my test script, I just updated:
product = Product(
code=code,
description=description,
related_workspace=related_workspace,
)
To:
product = Product(
code=code,
description=description,
related_workspace=Workspace.objects.get(pk=related_workspace)
)

Passing argument from view to Custom RelatedField serializer

How can I pass an argument to a serializers.RelatedField class from views.py. I need to pass language_id to query Language.objects model within that RelatedField.
I am not sure if I took a right approach to this issue. What I want to achieve is to present information about genres associated to a movie from database model about depending on the language. The MovieGenre model has genre ID field which I want to replace with actual Genre name.
My serialiser.py
class GenreField(serializers.RelatedField):
def to_representation(self, value, language_id=1):
genre_name = GenresVideo.objects.get(genre_id=value, language_id=language_id)
return genre_name.name
class MovieGenresSerializer(serializers.ModelSerializer):
genre_id = GenreField(read_only=True)
class Meta:
model = MoviesGenres
As you see, here I query Language.objects with default value but I would like to pass it from views (language_id).
My views.py:
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer
def list(self, request, language_pk):
queryset = MoviesGenres.objects.all()
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, movie, language_pk):
queryset = MoviesGenres.objects.filter(movie=movie)
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
And my urls.py:
router.register(r'lang', LanguagesViewSet, base_name='lang')
mov_gen = routers.NestedSimpleRouter(router, r'lang', lookup='language')
mov_gen.register(r'mg', MovieGenresTestViewSet, base_name='mg')
url(r'^api/', include(genre_spec.urls))
My models.py
class Languages(models.Model):
name = models.CharField(unique=True, max_length=255)
short_name = models.CharField(unique=True, max_length=4, blank=True, null=True)
active = models.BooleanField(default="")
class Meta:
managed = False
db_table = 'languages'
ordering = ('id',)
class GenresVideo(models.Model):
genre_id = models.IntegerField()
language = models.ForeignKey('Languages')
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'genres_video'
unique_together = (('genre_id', 'language'),)
ordering = ('genre_id',)
class MoviesGenres(models.Model):
movie = models.ForeignKey(Movies)
genre_id = models.IntegerField()
class Meta:
managed = False
db_table = 'movies_genres'
unique_together = (('movie', 'genre_id'),)
Through the urls routes, I can get a correct response from API including the language_id. I just need to pass it to the view somehow.
Thanks a lot for help!
I'll try to answer to your first question, with the easiest implementation possible: SerializerMethodField. Because we will get the language id via the context passed to the serializer, we should either generate the context for the serializer, or let the framework do that for us.
Now to the problem at hand: you aren't filtering the queryset (MoviesGenres) by language per se. Thus, we can avoid overwriting the list and retrieve methods. Nevertheless, the router mechanism will inject in kwargs for the view method the language_pk parameter - that's the parameter that we will retrieve from within the serializer context:
class MovieGenresSerializer(serializers.ModelSerializer):
genre = searializers.SerializerMethodField()
class Meta:
model = MoviesGenres
def get_genre(self, instance):
# get the language id from the view kwargs
language_id = self.context['view'].kwargs['language_pk']
# get the genre
try:
genre_name = GenresVideo.objects.get(genre_id=instance.genre_id, language_id=language_id).name
except GenresVideo.DoesNotExist:
genre_name = None
# return the formatted output
return genre_name
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer

How to add userprofile to UserDetailsSerializer in django

Trying to add userprofile to user model
using: django rest framework. rest-auth module
But line profile = instance.userprofile giving error:*** django.db.models.fields.related.RelatedObjectDoesNotExist: User has no userprofile.
following instructions from here
Also, not sure on what is happening in super statement
Possible errors:
1.instance is not having userprofile after the super statement, hence profile = instance.userprofile statement giving error2.userprofile needs to be added to UserDetailsSerializer
UserDetailsSerializer
class UserDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('username', 'email', 'first_name', 'last_name')
read_only_fields = ('email', )
UserSerializer
class UserSerializer(UserDetailsSerializer):
company_name = serializers.CharField(source="userprofile.company_name")
class Meta(UserDetailsSerializer.Meta):
fields = UserDetailsSerializer.Meta.fields + ('company_name',)
def update(self, instance, validated_data):
profile_data = validated_data.pop('userprofile', {})
company_name = profile_data.get('company_name')
instance = super(UserSerializer, self).update(instance, validated_data)
# get and update user profile
profile = instance.userprofile
if profile_data and company_name:
profile.company_name = company_name
profile.save()
return instance
Do ask for more clarity if required.
Thanks in advance.
In the documentation it is assumed that userprofile was already created and now can be updated. You just need a check
# get and update user profile
try:
profile = instance.userprofile
except UserProfile.DoesNotExist:
profile = UserProfile()
if profile_data and company_name:

Resources