django restframework _ OneToOne field save() - django

I have two models in my project. Model A and B. Model B has a 'OneToOne' relationship with model A. I wrote a serializer class for model B. in ".create()" function I have a problem with saving model B, because I need to override the save() function in B model for inserting Slug value. The error is:
save() got an unexpected keyword argument 'force_insert'
class A(models.Model):
address = models.Charfield(max_length=160)
class b(models.Model):
a = models.OneToOneField(AdIfo, related_name='ad_info', primary_key=True,
on_delete=models.CASCADE)
slug = models.SlugField(unique=True, db_index=True, blank=True)
def save(self):
self.slug ="%d%s" %(self.pk, slugify(self.title))
super(B, self).save()
serializers.py
class ASerializer(serializers.ModelSerializer):
class Meta:
model = A
fields = "__all__"
class BSerilizer(serializers.ModelSerializer):
a = ASerializer(many=False, required=False, allow_null=True)
slug = serializers.SlugField(read_only=True)
class Meta:
model = B
fields = '__all__'
def create(self, validated_data):
info_data = validated_data.pop('ad_info')
A.objects.create(**info_data)
ad = B.objects.update_or_create(**validated_data)
A.objects.update_or_create(ad_info=adgame, **info_data)
ad.save()
return ad

The problem is that you didn't accept the default arguments in B's save method. Generally when overriding a method you need to ensure you accept all the arguments that could be passed, and make sure you pass them on to the super class method. One way of doing that is with *args, **kwargs:
def save(self, *args, **kwargs):
self.slug ="%d%s" % (self.pk, slugify(self.title))
super(B, self).save(*args, **kwargs)

well your problem is you are trying to make model A and model B then.the model B depends on primary key of model A .this structure you made is making model A and there is no relating on model B and then you are creating model A again.
you just need to create model A and then model B but you should refer the model B id to model A. i know its confusing but try this :
def create(self, validated_data):
info_data = validated_data.pop('ad_info')
info = A.objects.create(**info_data)
ad = B.objects.create(pk=info.id, **validated_data)
return ad
pk=info.id is the key for this problem.

After you done the fix by #Daniel Roseman t think you should updated your validated_data with new instance of the A instance
def create(self, validated_data):
info_data = validated_data.pop('ad_info')
# Next two rows
a = A.objects.create(**info_data)
validated_data.update({'a': a})
ad = B.objects.update_or_create(**validated_data)
A.objects.update_or_create(ad_info=adgame, **info_data)
ad.save()
return ad

Related

Explicitly defining fields on ModelSerializer overrides Django's Model parameters like verbose_name and validators

Imagine having a simple model like the one bellow:
from utils.validators import name_validator
class Customer(models.Model):
name = models.CharField(verbose_name="Customer Name", validators=[name_validator])
email = models.EmailField(verbose_name="Customer Email")
def __str__(self):
return self.name
Now if I explicitly define a filed on my serializer, both validators and verbose_name are lost. I can use label= and validatos= when defining the field on my serializer but I don't want to repeat myself. What if I have multiple serializer pointing to the same Model?
class CustomerSerilizer(serializers.ModelSerializer):
custom_field_name = serializers.CharField(source="name")
class Meta:
model = Customer
fields = "__all__"
Is there anyway to prevent this from happening?
I'm not sure if it's the perfect way of doing this or not, but I managed to achieve my desired behavior by writing a custom ModelSerializer which sets label and validators if they are not being passed when explicitly defining a field on the serializer.
class CustomModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(CustomModelSerializer, self).__init__(*args, **kwargs)
model = self.Meta.model
model_fields = [f.name for f in model._meta.get_fields()]
for field_name, field_instance in self.fields.items():
source_field = field_instance.source
if source_field in model_fields:
model_field = model._meta.get_field(source_field)
if "label" not in field_instance._kwargs:
field_instance.label = model_field.verbose_name
if "validators" not in field_instance._kwargs:
field_instance.validators.extend(model_field.validators)

How to get the authentificated user information in Model Serializer in Django Rest?

I have a LinkList object, which has owner property - the username of the User it belongs to. I am trying to have LinkLists be linked to the user that made the create request, however self.context['request'].user and CurrentUserDefault don't work in my create method, so I cannot create an instance of the LinkList object.
The error I am getting:
NOT NULL constraint failed: llists_linklist.owner_id
Here is the serializer:
class LinkListSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name="lists-detail")
owner = serializers.ReadOnlyField(source='owner.username')
links = LinkSerializer(many=True)
class Meta:
model = LinkList
fields = ['url', 'owner', 'name', 'public', 'links']
def create(self, validated_data):
links = validated_data.pop('links', [])
instance = super().create(validated_data)
for item in links:
instance.links.add(link=item)
return instance
The model:
class LinkList(models.Model):
owner = models.ForeignKey(
User,
related_name='lists',
on_delete=models.CASCADE)
name = models.CharField(max_length=100)
description = models.CharField(max_length=250)
public = models.BooleanField(default=False)
links = models.ManyToManyField(
Link,
related_name='linklists')
def __str__(self):
return "%s - %s" % (self.owner, self.name)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
The problem is, the owner field is actually a required field and you are not passing it through the serializer as you made the owner field in the serializer read only.
Looks like you have two options:
1. You do not need the owner field to be returned
If so, just use the HiddenField() with CurrentUserDefault():
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
2. You need the owner field to be returned
If so, just pass the user manually to the serializer's save() method in the view's perform_create() method (I assume you are using generic view):
serializer.save(owner=self.request.user)

Use same serializer class but with different action in a view in Django rest framework

I have a modelset view in which different customs functions are defined based on the requirement. I have to write another get function in which I want to use the same serializer class. But the field which I have defined in the serializer class in pkfield but for the get function, I want it as a stringfield rather than pk field. How to achieve that??
Also, I have defined depth=1, which is also not working.
class Class(TimeStampAbstractModel):
teacher = models.ForeignKey(
Teacher,
on_delete=models.CASCADE,
null=True,
related_name="online_class",
)
subject = models.ForeignKey(
Subject,
on_delete=models.SET_NULL,
null= True,
related_name= "online_class",
)
students_in_class = models.ManyToManyField(Student, related_name="online_class")
My view:
class ClassView(viewsets.ModelViewSet):
queryset = Class.objects.all()
serializer_class = ClassSerializer
serializer_action_classes = {
'add_remove_students': AddStudentstoClassSerializer,
'get_all_students_of_a_class': AddStudentstoClassSerializer,
}
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 add_remove_students(self, request, *args, **kwargs):
"""
serializer class used is AddStudentstoClassSerializer
"""
def get_all_students_of_a_class(self,request,pk=None):
"""
for this I function too, I want to use the same AddStudentstoClassSerializer class but
there is a problem. The field students_in_class is already defined as pkfield, whereas I
want to use it as a stringfields in the response of this function
""""
My serializer:
class AddStudentstoClassSerializer(serializers.ModelSerializer):
students_in_class = serializers.PrimaryKeyRelatedField(
many=True, queryset=Student.objects.all()
)
class Meta:
model = Class
fields = ["students_in_class"]
depth = 1
def update(self, instance, validated_data):
slug = self.context["slug"]
stu = validated_data.pop("students_in_class")
/................other codes....../
return instance
Here we can see the student_in_class is defined as pkfield which is ok when using the update api, but when I want to use the get api and call get_all_students_of_a_class I want the field to be stringfield or some other field. How to do that? Also depth= 1 is also not working.
Update:
Treid the following but still not working:
def to_representation(self, instance):
rep = super().to_representation(instance)
# rep["students_in_class"] = instance.students_in_class
rep['students_in_class'] = StudentSerializer(instance.students_in_class).data
return rep
class StudentSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Student
fields = ['user', 'college_name', 'address']
what i got in the response is
{
"students_in_class": {}
}
it is empty dict. what should be done!
You can override you to_representation method like this.
class AddStudentstoClassSerializer(serializers.ModelSerializer):
students_in_class = serializers.PrimaryKeyRelatedField(
many=True, queryset=Student.objects.all()
)
class Meta:
model = Class
fields = ["students_in_class"]
def to_representation(self, instance):
data = {
"students_in_class": # Write your logic here
}
return data
def update(self, instance, validated_data):
slug = self.context["slug"]
stu = validated_data.pop("students_in_class")
/................other codes....../
return instance

Field is required on foreign key error Django rest Framework

I am in a issue in which my api is creating a duplicate data as I am just passing ingredient name and its restaurant not Pk etc. So to prevent this thing I made a
class Meta:
unique_together = ('restaurant' ,'name')
constraint in my model . Before this everything was fine just duplicate entries were creating. Now after adding this constraint its saying 'Restaurant field is required' and my serializer is not valid.
My Ingredient model is like this
class Ingredient(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE )
name = models.CharField(max_length=255 ,)
class Meta:
unique_together = ('restaurant' ,'name')
def __str__(self):
return str(self.name)
and my Ingredient Serializer is like
class IngredientsSerializer(serializers.ModelSerializer):
restaurant = RestaurantSerializer(required=False)
class Meta:
model = Ingredient
fields = '__all__'
def create(self, validated_data):
restaurant = validated_data.get('restaurant')
name = validated_data.get('name', None)
ingredient = Ingredient.objects.create(restaurant=restaurant, name=name)
return ingredient
And my view.py for serialize is like
#permission_classes([AllowAny])
class CreateIngredients(APIView):
def post(self, request):
serializer = IngredientsSerializer(data=request.data)
if serializer.is_valid():
restaurant=Restaurant.objects.get(id=request.POST['restaurant'])
obj_article = serializer.save(restaurant=restaurant)
return Response(success_response(data='none', msg='Ingredient added'), status=status.HTTP_200_OK)
Looks like you are not sending restaurant in your request.
Even though in your serializer you've defined that restaurant isn't required.
restaurant = RestaurantSerializer(required=False)
What makes it invalid is the create method. In there you have
ingredient = Ingredient.objects.create(restaurant=restaurant, name=name)
which uses objects create method (your restaurant argument here is probably None) and since you've defined unique together with restaurant and name this means neither of them can be None.

Foreign Key - get values

I have created the following models. Many Accounts belong to one Computer.
class Computer(models.Model):
name = models.CharField(max_length=256)
def __str__(self):
return str(self.name)
class Accounts(models.Model):
computer = models.ForeignKey(Computer, on_delete=models.CASCADE, related_name='related_name_accounts')
username = models.CharField(max_length=10)
def __str__(self):
return str(self.username)
I now want to create a Form where i can choose one user from a dropdown-list of all users that belong to a Computer
class ComputerForm(forms.ModelForm):
class Meta:
model = Computer
exclude = ['name',]
def __init__(self, *args, **kwargs):
super(ComputerForm, self).__init__(*args, **kwargs)
self.fields['user']=forms.ModelChoiceField(queryset=Computer.related_name_accounts.all())
But i get an error
'ReverseManyToOneDescriptor' object has no attribute 'all'
How do i have to adjust the queryset in __init__ to display all users of a computer?
Edit:
>>> x = Computer.objects.filter(pk=3).get()
>>> x.related_name_accounts.all()
I would somehow need to pass the dynamic pk to the ModelForm?
Your issue is that you used a capital C in Computer.related_name_accounts.all(). This doesn't make sense, because the Computer model doesn't have related fields - it's instances do.
This line of code would work:
computer = Computer.objects.get(id=1)
computer.related_name_accounts.all()
But the issue is, I am not sure what you mean by ...of all users that belong to a Computer, since you don't have a ForeignKey or OneToOne relationship between User and Computer.
You can pass an instance of your model to the form like this:
x = Computer.objects.get(pk=3)
form = ComputerForm(instace=x)
And access it through self.instance in the form methods:
class ComputerForm(forms.ModelForm):
class Meta:
model = Computer
exclude = ['name',]
def __init__(self, *args, **kwargs):
super(ComputerForm, self).__init__(*args, **kwargs)
self.fields['user']=forms.ModelChoiceField(
queryset=self.instance.related_name_accounts.all()
)