Extended User Model not updating - python

Hii I'am new to Django rest frame work and was preparing API's So this is my
models.py
class User(auth.models.User, auth.models.PermissionsMixin):
def __str__(self):
return "#{}".format(self.username)
class User_log(models.Model):
user = models.OneToOneField(auth.models.User,on_delete=models.CASCADE,related_name='user_logs')
fullname=models.CharField(max_length=255)
fb_login=models.BooleanField(default=False)
def __str__(self):
return self.fullname
serializers.py
class userSerializers(serializers.ModelSerializer):
fullname = serializers.StringRelatedField(source='user_logs.fullname',read_only=False)
fb=serializers.BooleanField(source='user_logs.fb_login')
class Meta:
model = User
fields=('id','username','email','fullname','fb')
related_fields = ['user_logs']
def update(self, instance, validated_data):
# Handle related objects
for related_obj_name in self.Meta.related_fields:
print('exe')
print(instance,validated_data)
# Validated data will show the nested structure
data = validated_data.pop(related_obj_name)
related_instance = getattr(instance, related_obj_name)
# Same as default update implementation
for attr_name, value in data.items():
setattr(related_instance, attr_name, value)
related_instance.save()
return super(userSerializers,self).update(instance, validated_data)
viewset.py
class Userview(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class=userSerializers
Now the problem: whenever i try to update fullname column through json request
{ "id": 2,"username": "karam","email": "karam#83ideas.com","fullname": "karm","fb": true } i am getting this error "POST /accounts/accounts/2/ HTTP/1.1" 405 41
and as in serializers.py update method i have printed the validated data instead of this
karam {'username': 'karam', 'email': 'karam#83ideas.com', 'user_logs': {'fullname':'karam','fb_login': True}}
i am getting this
karam {'username': 'karam', 'email': 'karam#83ideas.com', 'user_logs': {'fb_login': True}}
SO any idea how to resolve it?

Instead of using StringRelatedField in serializers.py use CharField.
class userSerializers(serializers.ModelSerializer):
fullname=serializers.CharField(source='user_logs.fullname')
fb=serializers.BooleanField(source='user_logs.fb_login')
class Meta:
model = User
fields=('id','username','email','fullname','fb')
related_fields = ['user_logs']

Related

AttributeError: 'Serializer' object has no attribute 'Meta' in django rest framework

I am using serializers.Serializer instead of ModelSerializer which doesn't require Meta class but it keep saying object has no attribute Meta. Iam not sure what is the issue but when I run the localserver, the main page gives error saying api fetch error and in the terminal it says AttributeError: 'Serializer' object has no attribute 'Meta'.
My view:
class ClassView(viewsets.ModelViewSet):
queryset = Class.objects.all().order_by('-created_at')
serializer_class = ClassSerializer
serializer_action_classes = {
'get_all_students_of_a_class': ClassDetailSerializer,
}
# .annotate(total_students=Count('students_in_class'))
def get_serializer_class(self):
"""
returns a serializer class based on the action
that has been defined.
"""
try:
return self.serializer_action_classes[self.action]
except (KeyError, AttributeError):
return super(ClassView, self).get_serializer_class()
def get_all_students_of_a_class(self,request,pk=None):
"""
returns a class details with all the students signed up for the
class along with the subject.
"""
id = pk
if self.is_teacher(id):
online_classes = get_object_or_404(Class, id=id)
students = Student.objects.filter(online_class__id=id)
subject_details = Subject.objects.get(online_class__id=id)
total_students_in_class = online_classes.total_students_in_class
created_at = online_classes.created_at
updated_at = online_classes.updated_at
data = {
"teacher": self.get_teacher_instance(),
'total_students_in_class': total_students_in_class,
"students_in_class": students,
"subject": subject_details,
'created_at': created_at,
'last_updated_at': updated_at,
}
serializer_class = self.get_serializer_class()
serializer = serializer_class(data)
return Response(serializer.data, status=status.HTTP_200_OK)
My serializer:
class ClassDetailSerializer(serializers.Serializer):
teacher = serializers.StringRelatedField()
subject = SubjectLevelSerializer()
total_students_in_class = serializers.ReadOnlyField()
students_in_class = serializers.StringRelatedField(many=True)
created_at = serializers.DateTimeField(read_only=True)
last_updated_at = serializers.DateTimeField(read_only=True)
My url:
path("class/<int:pk>/",teacher.ClassView.as_view({"get": "get_all_students_of_a_class","delete":"destroy"}),
),
However it works and I can perform action if I go to localhost/admin and other api calls from localhost.
Add a Meta class with model = YourModel
class ExampleDetailSerializer(serializers.Serializer):
employee = serializers.StringRelatedField()
person = PersonSerializer()
class Meta:
model = Example # model name
fields = ('__all__')
Try to use "ViewSet" instead of "ModelViewSet". And while using ViewSet make sure to define list, create, etc. function by your own.

DRF- Error when creating a new instance in an M2M through model

I have the following two models:
class User(models.Model):
user_id = models.CharField(
max_length=129,
unique=True,
)
user_article = models.ManyToManyField(
Article,
through="UserArticle",
)
occupation = models.CharField(max_length=100, default='null')
def __str__(self):
return self.user_id
and
class Article(models.Model):
uuid = models.UUIDField(editable=False, unique=True)
company = models.ForeignKey(
Company,
on_delete=models.PROTECT,
related_name='article_company_id',
)
articleType = models.ForeignKey(
ArticleType,
on_delete=models.PROTECT,
related_name='type',
)
date_inserted = models.DateField()
def __str__(self):
return self.uuid
which are modeled with a many-to-many relationship, using this through model:
class UserArticle(models.Model):
user = models.ForeignKey(User, to_field='user_id',
on_delete=models.PROTECT,)
article = models.ForeignKey(Article, to_field='uuid',
on_delete=models.PROTECT,)
posted_as = ArrayField(
models.CharField(max_length=100, blank=True),)
post_date = models.DateField()
class Meta:
db_table = "core_user_articles"
Here's my view:
class BatchUserArticleList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = UserArticle.objects.all()
serializer_class = BatchUserArticleSerializer
def create(self, request, *args, **kwargs):
serializer = BatchUserArticleSerializer(data=request.data)
if not serializer.is_valid():
return response.Response({'Message': 'POST failed',
'Errors': serializer.errors},
status.HTTP_400_BAD_REQUEST)
self.perform_create(serializer) # equal to serializer.save()
return response.Response(serializer.data, status.HTTP_201_CREATED)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
The problem I'm facing is when I want to POST data, of the following format, in the M2M table:
{
"posted_as": ["news"],
"post_date": "2020-05-26",
"user": "jhtpo9jkj4WVQc0000GXk0zkkhv7u",
"article": [
"11111111",
"22222222"
]
}
The above contains a list of many articles so I used a custom field in my serializer in order to extract each article, create a new UserArticle object and insert it, using bulk_create, into my M2M table. I think that's the way to go when the incoming data do not map exactly to the DB model, but I might be wrong. So please comment if you see something off with this approach.
Here is the serializer:
class BatchUserArticleSerializer(serializers.ModelSerializer):
article= ArticleField(source='*') #custom field
class Meta:
model = UserArticle
fields = ('posted_as', 'post_date', 'user', 'article')
def validate(self, data):
post_date = data['post_date']
if post_date != date.today():
raise serializers.ValidationError(
'post_date: post_date is not valid',
)
return data
def create(self, validated_data):
post_as = list(map(lambda item: item, validated_data['posted_as']))
post_date = validated_data['post_date']
user = validated_data['user']
list_of_articles = validated_data['article']
user_object = User.objects.get(user_id=user)
articles_objects = list(map(lambda res: Article.objects.get(uuid=res), list_of_articles))
user_articles_to_insert = list(map(
lambda article: UserArticle(
posted_as=posted_as,
post_date=post_date,
article=article,
user=user_object),
articles_objects))
try:
created_user_articles = UserArticles.objects.bulk_create(user_articles_to_insert)
for res in created_user_articles:
res.save()
return created_user_articles
except Exception as error:
raise Exception('Something went wrong: {0}'.format(error))
and
class ArticleField(serializers.Field):
def to_representation(self, value):
resource_repr = [value.article]
return resource_repr
def to_internal_value(self, data):
internal_repr = {
'article': data
}
return internal_repr
This seems to work ok as I can see data being correctly inserted in the UserArticle table:
id | posted_as | post_date | user | article
1 | news | 2020-05-26 | jhtpo9jkj4WVQc0000GXk0zkkhv7u | 11111111
2 | news | 2020-05-26 | jhtpo9jkj4WVQc0000GXk0zkkhv7u | 22222222
The problem comes when code reaches this line:
response.Response(serializer.data, status.HTTP_201_CREATED)
and more specifically, the error I'm getting is:
AttributeError: Got AttributeError when attempting to get a value for field `posted_as` on serializer `BatchUserArticleSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `list` instance. Original exception text was: 'list' object has no attribute 'posted_as'.
The original exception error is raised at the instance = getattr(instance, attr) line of the def get_attribute(instance, attrs) function in the fields.py DRF source.
What am I missing here?
First of all, there is no reason to call save method for each of bulk-created instances.
Second one is reason of exception. You call create viewset method. it calling serializers create method which must return only one instance (created object). but your serializer returns list created_user_articles. List really have no field posted_as.
So, there is two ways to fix it.
First one is override create method in view, to change the way of data representation. For ex. use another serializer for response data:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
created_user_articles = self.perform_create(serializer)
# use another way to get representation
response_data = AnotherUserArticleSerializer(created_user_articles, many=True).data
return Response(response_data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
# add return to get created objects
return serializer.save()
Second one is return only one instance in create method of your serializer.

Django REST framwork mongoengine ValueError: The source SON object needs to be of type 'dict'

I'm working a project, and use Django REST framework and mongo engine, and I'm confused a question two days, and the detail see below:
class Jvv(EmbeddedDocument):
unit = fields.StringField()
unitValue = fields.IntField()
class Meta:
db_table = 'imagerecognition'
class ImageRecognition(Document):
imageUrl = fields.StringField(default='', max_length=100)
createTime = fields.DateTimeField(default=datetime.now())
ddPercent = fields.FloatField(required=False, default='')
jvv = fields.ListField(fields.EmbeddedDocumentField(Jvv))
def __str__(self):
return self.imageUrl
class Meta:
db_table = 'imagerecognition'
then the serializer.p document is :
class JvvSerializer(mongoserializers.EmbeddedDocumentSerializer):
class Meta:
model = Jvv
fields = '__all__'
class ImageUrlSerializer(mongoserializers.DocumentSerializer):
jvv = JvvSerializer(many=True)
class Meta:
model = ImageRecognition
fields = ('imageUrl', 'createTime', 'ddPercent', 'jvv')
and the views.py content is below:
class ImageUrlSave(views.APIView):
def get(self, request, *args, **kwargs):
imgs = ImageRecognition.objects(imageUrl='白菜')
serializer = ImageUrlSerializer(imgs, many=True)
ImageRecognition(imageUrl='土豆', ddPercent=8.22, jvv={'unit':'m', 'unitValue':12}).save()
data = serializer.data
return Response({
'msg': 'SUCCESS',
'code_status': 1000,
'result': data
})
the question is the mongodatabases have been completed, I want to take some data from it, but when I runserver, it shows
raise ValueError("The source SON object needs to be of type 'dict'")
ValueError: The source SON object needs to be of type 'dict', how can I handle this problem, and I am Looking forward to get answer. Thank you.

Rest framework: different serializer for input and output data on post/put operations

Lets say I have these models:
class Download(MPTTTimeStampedModel):
endpoint = models.ForeignKey(EndPoint, related_name="downloads",)
class EndPoint(TimeStampedModel):
name = models.CharField(max_length=100, verbose_name=_(u"Nombre"))
url = models.CharField(max_length=2000, verbose_name=_(u"Url"))
These serializers:
class DownloadSerializer(serializers.ModelSerializer):
class Meta:
model = Download
fields = ('id', 'endpoint')
def create(self, validated_data):
...
def update(self, validated_data):
...
class EndPointSerializer(serializers.ModelSerializer):
class Meta:
model = EndPoint
fields = ('id', 'name', 'url')
def create(self, validated_data):
...
def update(self, validated_data):
...
And this generic api view:
class DownloadList(generics.ListCreateAPIView):
queryset = Download.objects.all()
serializer_class = DownloadSerializer
This will allow me to create a download by sending a json representation looking like this:
{
'id': null,
'endpoint': 19
}
And upon creation, the web service will send me back the data with the id from the database. Now, I actually want the web service to send me back not just the endpoint id but a complete representation of the object, Something like this:
{
'id': null,
'endpoint': {
'id': 19,
'name': 'my endpoint',
'url': 'http://www.my-endpoint.com/'
}
}
I would manage this with this serializer:
class DownloadDetailedSerializer(DownloadSerializer):
endpoint = EndPointSerializer(many = False, read_only=False)
And now the actual question: how do i tell my generic view to use this last serializer for the returned data while keeping the original DownloadSerializer for the input?
EDIT: as #neverwalkeralone suggested the way to go is creating a custom field and overriding to_representation method. But that lead me to an exception in the line serializer = EndPointSerializer(value), and after some testing I found out that it would be better to inherit my custom field from RelatedField. That implies overriding to_internal_value too. So here is what finally got the job done:
class EndPointField(serializers.RelatedField):
def to_representation(self, value):
serializer = EndPointSerializer(value)
return serializer.data
def to_internal_value(self, instance):
endpoint = EndPoint.objects.get(pk=instance)
return endpoint
def get_queryset(self):
return EndPoint.objects.all()
You can define custom field, use to_representation method to customize output format:
class EndPointField(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
serializer = EndPointSerializer(value)
return serializer.data
def get_queryset(self):
return models.EndPoint.objects.all()
And use it in DownloadSerializer for endpoint field:
class DownloadSerializer(serializers.ModelSerializer):
endpoint = EndPointField()
class Meta:
model = Download
fields = ('id', 'endpoint')
UPD
Based on Kilian Perdomo Curbelo feedback EndPointField's to_representation value should be replaced with endpoint instance:
def to_representation(self, value):
endpoint = EndPoint.objects.get(pk=value.pk)
serializer = EndPointSerializer(endpoint)
return serializer.data

ModelViewSet - Update nested field

I'm currently working on Django with Django Rest Framwork.
I can't update my object within nested object field.
serializer.py
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
class CarSerializer(serializers.ModelSerializer):
owner = ownerSerializer(many=False, read_only=False)
class Meta:
model = Car
fields = ('id', 'name', 'owner')
view.py
class OwnerViewSet(viewsets.ModelViewSet):
queryset = Owner.objects.all()
serializer_class = OwnerSerializer
class CarViewSet(viewsets.ModelViewSet):
serializer_class = CarSerializer
queryset = Car.objects.all()
def create(self, request):
serialized = self.serializer_class(data=request.DATA)
if serialized.is_valid():
serialized.save()
return Response(status=HTTP_202_ACCEPTED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
When I do this :
Request URL:http://localhost:9000/api/v1/cars/1/?format=json
Request Method:PUT
Request Paylod :
{
"id":1,
"name": "TEST",
"ower": {
"id":1,
"name": "owner_test"
}
}
I get the following Response :
The `.update()` method does not support writable nestedfields by default.
Write an explicit `.update()` method for serializer `app.serializers.CarSerializer`,
or set `read_only=True` on nested serializer fields.
Knowing :
I want to keep the owner serialization on GET;
We can imagine the car nested by another object and ect...
How can I do if i want to change the owner when I update the car.
A little late, but, Try this,
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
extra_kwargs = {
'id': {
'read_only': False,
'required': True
}
} #very important
def create(self, validated_data):
# As before.
...
def update(self, instance, validated_data):
# Update the instance
instance.some_field = validated_data['some_field']
instance.save()
# Delete any detail not included in the request
owner_ids = [item['owner_id'] for item in validated_data['owners']]
for owner in cars.owners.all():
if owner.id not in owner_ids:
owner.delete()
# Create or update owner
for owner in validated_data['owners']:
ownerObj = Owner.objects.get(pk=item['id'])
if ownerObje:
ownerObj.some_field=item['some_field']
....fields...
else:
ownerObj = Owner.create(car=instance,**owner)
ownerObj.save()
return instance
Just in-case someone stumbles on this
had the same error in my case but setting read_only to True fixed it for me.
owner = ownerSerializer(many=False, read_only=True)
Note that, this field won't appear in the form when posting data to the api.

Categories