How to validate unique constraint on django rest framework? - python

How can i validate an unique constraint with a key that is not on the request payload?
The key that i need to validate are user_id and sku but the request does not contain the user_id key.
Example of payload:
{'sku': '123', data: []}
The serializers:
class ProductConfiguration(serializers.Serializer):
min_quantity = serializers.IntegerField(required=True)
price = serializers.DecimalField(
required=True,
decimal_places=2,
max_digits=10,
coerce_to_string=False
)
class ProductSerializer(serializers.ModelSerializer):
sku = serializers.CharField(required=True)
data = ProductConfiguration(many=True, required=True)
class Meta:
model = WholeSale
# the "id" and "user_id" columns should not be included on the response
exclude = ['id', 'user']
I need to validate that the user and sku key already exist.
By default if the two keys user_id and sku were on the payload drf could take care of Unique error, how can i validate this two keys if one of them are not on the payload?

you can get user data from request
request.user
Maybe pass it in to serializer from view
data = request.data
data['user_id'] = request.user.pk
serializer = ProductSerializer(data)
in serializer you could do
def validate(self, data):
user = data.get('user_id')
sku = data.get('sku')
record = WholeSale.objects.filter(user=user, sku=sku).first()
if not record:
raise serializers.ValidationError("This combo doesn't exist")
return super().validate(data)

Assuming that you have this model structure ( i am only taking the sku and user field). To achieve what you trying to do, in class meta provide a unique together constraints,
class WholeSale(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
sku = models.CharField(max_lenght=100)
class Meta:
unique_together = ['user', 'sku']
OR,
simply overwrite the validate_unique method to achieve validation on both user and sku fields together.
class WholeSale(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
sku = models.CharField(max_lenght=100)
def validate_unique(self, *args, **kwargs):
# super(WholeSale, self).validate_unique(*args, **kwargs) # python2
super().validate_unique(*args, **kwargs) # python3
if self.__class__.objects.filter(user=self.user, sku=self.sku).\
exists():
raise ValidationError(
message='This (user, sku) already exists.',
)
No need to validate explicitly from serializer

class ProductSerializer(serializers.ModelSerializer):
def validate(self, attrs):
if not Sku.objects.get(sku=attrs['sku']).exists() or not User.objects.get(id=attrs['id']).exists():
raise serializers.ValidationError("Something doesn't exist")
return attrs
sku = serializers.CharField(required=True)
data = ProductConfiguration(many=True, required=True)
class Meta:
model = WholeSale
exclude = ['id', 'user']

Related

How to fetch data by foreign key in Django serializers?

I'm stuck on fetching data with a related foreign key. I am doing some kind of validation in which the create method would be allowed depending on the requesterid to be POSTed.
This is my userTable model:
class userTable(models.Model):
userid = models.UUIDField(primary_key=True, default = uuid.uuid4, editable=False)
username = models.CharField(max_length=50, null=False, unique=True)
userroleid = models.ForeignKey(roleTable, on_delete=models.CASCADE)
def __str__(self):
return self.username
Another model is the requestTable
class requestTable(models.Model):
rid = models.AutoField(primary_key=True)
requesterid = models.ForeignKey(userTable, on_delete=models.CASCADE)
(...)
This is my serializer:
class RequestCreateSerializer(serializers.ModelSerializer):
parts=PartSerializer(many=True)
class Meta:
model = requestTable
fields = ['rid','requesterid', (...)]
def create(self, instance, validated_data):
if instance.requesterid.userroleid == 3: #how can i fetch my `requesterid` data?
parts_data = validated_data.pop('parts')
request = requestTable.objects.create(**validated_data)
for part_data in parts_data:
partsTable.objects.create(request=request, **part_data)
return request
raise ValidationError("Sorry! Your role has no permission to create a request.")
Also, I'm quite confused if I should do validation in serializers, or views. If I do it in views, it just throws the ValidationError, and it doesn't seem to enter the if condition.
For reference, here's my views:
class RequestListCreateView(ListCreateAPIView):
queryset = requestTable.objects.all()
serializer_class = RequestCreateSerializer
def create(self, request, *args, **kwargs):
if request.data.get('requesterid__userroleid') == '3':
write_serializer = RequestCreateSerializer(data=request.data)
write_serializer.is_valid(raise_exception=True)
self.perform_create(write_serializer)
headers = self.get_success_headers(write_serializer.data)
return Response({"Request ID": write_serializer.instance.requestid, "Parts ID": [p.partsid for p in write_serializer.instance.parts.all()]},headers=headers)
raise ValidationError("Sorry! Your role has no permission to create a request.")
Hope someone can help me!

null value in column "assigned_facilities_id"

I'm trying to access the dictonary inside the jsonfield serializer "assigned_facilities". But i'm receiving the following error:
django.db.utils.IntegrityError: null value in column "assigned_facilities_id" of relation "users_leadfacilityassign" violates not-null constraint
DETAIL: Failing row contains (78, null, null, 159).
File "/app/users/api/views.py", line 53, in perform_create
serializer.save(agent=self.request.user)
File "/usr/local/lib/python3.9/site-packages/rest_framework/serializers.py", line 205, in save
self.instance = self.create(validated_data)
File "/app/users/api/serializers.py", line 252, in create
instance.leadfacility.create(assigned_facilities_id=assigned_facilities.get('facility_id'), datetime=assigned_facilities.get('datetime'))
I'm basically trying to create a "LeadFacilityAssign" object for each item inside my json so i can have a "LeadFacilityAssign" object for each facility i want to add to a lead.
Does anyone know what is causing this error? I tried a few different things but nothing worked so far.
json
{
"facilities": [{
"facility_id": "1",
"datetime": "2018-12-19 09:26:03.478039"
},
{
"facility_id": "1",
"datetime": "2018-12-19 09:26:03.478039"
}
]
}
serializers.py
class LeadUpdateSerializer(serializers.ModelSerializer):
is_owner = serializers.SerializerMethodField()
assigned_facilities = serializers.JSONField(required=False, allow_null=True, write_only=True)
class Meta:
model = Lead
fields = (
"id",
"first_name",
"last_name",
"is_owner",
"assigned_facilities",
)
read_only_fields = ("id", "is_owner")
def get_is_owner(self, obj):
user = self.context["request"].user
return obj.agent == user
def create(self, validated_data):
assigned_facilities = validated_data.pop("assigned_facilities")
instance = Lead.objects.create(**validated_data)
for facility in assigned_facilities:
instance.leadfacility.create(assigned_facilities_id=assigned_facilities.get('facility_id'), datetime=assigned_facilities.get("datetime"))
return instance
models.py
class Facility(models.Model):
name = models.CharField(max_length=150, null=True, blank=False)
def __str__(self):
return self.name
class Lead(models.Model):
first_name = models.CharField(max_length=40, null=True, blank=True)
last_name = models.CharField(max_length=40, null=True, blank=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class LeadFacilityAssign(models.Model):
assigned_facilities = models.ForeignKey(Facility, on_delete=models.CASCADE, related_name='leadfacility')
lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name='leadfacility')
datetime = models.DateTimeField()
views.py
class LeadCreateView(CreateAPIView):
permission_classes = [IsAuthenticated, IsLeadOwner]
serializer_class = LeadUpdateSerializer
def perform_create(self, serializer):
serializer.save(agent=self.request.user)
class LeadUpdateView(UpdateAPIView):
permission_classes = [IsAuthenticated, IsLeadOwner]
serializer_class = LeadUpdateSerializer
def get_queryset(self):
return Lead.objects.all()
You have three tables:
Lead table in which lead_id is non nullable since it is primary key
Facility table in which facility_id is non nullable since it is primary key
LeadFacility table in which lead_facility_id is non nullable but its two foreign keys (lead_id and facility_id) are nullable.
And you are assigning these nullable value to your non nullable field.
Maybe you are trying to do :
lead_falility_id (table: LeadFacility talbe) = facility_id (table: Facility)
But by mistake you are doing :
lead_falility_id (table: LeadFacility talbe) = facility_id (table: LeadFacility).
And because of this, your are doing :
lead_facility_id = null for the non nullable field.
Your JSON doesn't match your serializer.
Your serializer fields don't match your model fields.
Your views don't match your models or your serializers.
So, let's take it from the top.
If I understand correctly, you want to create a LeadFacilityAssign object at the same time as creating or updating a Lead object. There are some approaches to solve this, like using a post_save signal right after a Lead create request, but let's follow your drift...
From your Lead serializer, this is "fine":
class LeadUpdateSerializer(serializers.ModelSerializer):
is_owner = serializers.SerializerMethodField()
assigned_facilities = serializers.JSONField(required=False, allow_null=True, write_only=True)
class Meta:
model = Lead
fields = (
"id",
"first_name",
"last_name",
"is_owner",
"assigned_facilities",
)
read_only_fields = ("id", "is_owner")
But this:
def get_is_owner(self, obj):
user = self.context["request"].user
return obj.agent == user
Has a comparison statement (==) in the last line, meaning it could return a True or False value (?), plus you're not using the "agent" field anywhere else, nor is declared in the serializer or even a field in your models. Just get rid of that function or add the "agent" field in your LeadFacilityAssign model (assuming you'll assign a Lead, a Facility and an Agent to that relationship).
What I'm guessing you're expecting from you JSON call is the "facilities" info. From your declared fields above I guess you should be expecting an "assigned_facilities" field, which doesn't show in your JSON data, but let's assume your API will receive an "assigned_facilities" field instead of the "facilities" sub-dict with many facilities related to a single Lead.
I haven't tested the code below, but according to the REST API docs, you have to define two methods in your serializer now, one for CREATE and one for UPDATE.
Create:
def create(self, validated_data):
lead = Lead.objects.create(first_name=validated_data['first_name'], last_name=validated_data['last_name'] #Here you will create the Lead object that you will reference later in your LeadFacilityAssign relationship with the dictionary information from the received data, so let's save it:
lead.save()
#Now we need to create all facilities relationships to this Lead:
facilities = validated_data['assigned_facilities'] #This will create a "facilities" sub-dict from your received data with a facility_id and a datetime field in key-value pair.
for item in facilities:
facility = Facility.objects.get(id=item['facility_id']) #Get a single facility object for each ID in your JSON. If this fails, try converting it to int().
datetime = item['datetime'] #Again, if it fails because it's taken as string, try converting it to datetime object.
entry = LeadFacilityAssign.objects.create(assigned_facilities=facility, lead=lead, datetime=datetime) #Create the entry.
entry.save() #Save the entry
return #Exit your function
The Update method should look more or less the same.
In the view, if you're not using the "agent" field just parse the user it for safety or just use it later if you want to include it as owner in your model.
class LeadCreateView(CreateAPIView):
permission_classes = [IsAuthenticated, IsLeadOwner]
serializer_class = LeadUpdateSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Your JSON sample include "facilities" but your serializer has assigned_facilities = serializers.JSONField(required=False, allow_null=True, write_only=True).
class LeadUpdateSerializer(serializers.ModelSerializer):
is_owner = serializers.SerializerMethodField()
facilities = serializers.JSONField(required=False, allow_null=True, write_only=True)
class Meta:
model = Lead
fields = (
"id",
"first_name",
"last_name",
"PrimaryAddress",
"City",
"PostalCode",
"RegionOrState",
"pc_email",
"Cell",
"secphone",
"client_cell",
"client_secphone",
"birthday",
"curr_client_address",
"curr_client_city",
"curr_client_zip",
"ideal_address",
"ideal_city",
"ideal_zip",
"ideal_state",
"budget",
"client_email",
"client_first_name",
"client_last_name",
"lead_status",
"created_at",
"agent",
"is_owner",
"relationship",
"marital_status",
"gender",
"pets",
"facilities",
)
read_only_fields = ("id", "created_at", "agent", "is_owner")
def get_is_owner(self, obj):
user = self.context["request"].user
return obj.agent == user
def create(self, validated_data):
facilities = validated_data.pop("facilities", None)
instance = Lead.objects.create(**validated_data)
for facilities in assigned_facilities:
LeadFacilityAssign.objects.create(assigned_facilities_id=assigned_facilities.get("facility_id"), datetime=assigned_facilities.get("datetime"), lead=instance)
return instance
Also you added required False for facilities, so validated.pop("facilities") might be caused error if there is no facilities in request. You should add another parameter into pop method. validated.pop("facilities", None)
Your Query will become like this
instance.leadfacility.create(assigned_facilities_id__id=assigned_facilities.get('facility_id'), datetime=assigned_facilities.get("datetime"))
NOTE-
because assigned_facilities_id return full object of foreign key & assigned_facilities_id__id return value of id foreign key object

Django Rest Framework: TypeError - Direct assignment to the forward side of a many-to-many set is prohibited

I have a custom User model and a Group model that are linked by a UserGroup through model (Many to Many relationship):
models.py
class User(models.Model):
username = models.CharField(primary_key=True, max_length=32, unique=True)
user_email = models.EmailField(max_length=32, unique=False) # Validates an email through predefined regex which checks ‘#’ and a ‘.’
user_password = models.CharField(max_length=32)
user_avatar_path = models.CharField(max_length=64)
class Group(models.Model):
group_id = models.AutoField(primary_key=True)
group_name = models.CharField(max_length=32, unique=False)
group_admin = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='my_groups'
)
members = models.ManyToManyField(
User,
related_name='groups', # The name to use for the relation from the related object back to this one.
through='UserGroup' # Attaches a Junction table to the Many to Many relationship.
)
class UserGroup(models.Model): # Manually specified Junction table for User and Group
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='user_groups'
)
group = models.ForeignKey(
Group,
on_delete=models.CASCADE,
related_name='user_groups'
)
I'm trying to associate multiple users with a group, using a PATCH request to update the members attribute of a group. Using the following GroupSerializer, I'm able to associate a user as a member of the group when the group is created, by overriding the create function of the serializer:
serializers.py
class GroupSerializer(serializers.ModelSerializer):
members = MemberSerializer(many=True, required=False)
group_admin = serializers.SlugRelatedField(slug_field='username', queryset=User.objects.all()) # A Group object is related to a User object by username
class Meta:
model = Group
fields = ['group_id', 'group_name', 'group_admin', 'members']
def create(self, validated_data): # Overriden so that when a group is created, the group admin is automatically declared as a member.
group = Group.objects.create(**validated_data)
group_admin_data = validated_data.pop('group_admin')
group.members.add(group_admin_data)
return group
def update(self, instance, validated_data):
members_data = validated_data.pop('members') # Comes from the request body, gets the members list
#print('output: ' + str(members_data[0].items()))
add_remove = self.context['add_remove'] # Comes from the View
if members_data is not None:
if add_remove == 'add':
for member in members_data:
instance.members.add(member['username'])
elif add_remove == 'remove':
for member in members_data:
instance.members.remove(member['username'])
return super().update(instance, validated_data)
I'm not able to update the members associated with a group when overriding the update function of the serializer. The serializer is called from the following GroupUpdate view:
views.py
class GroupUpdate(generics.UpdateAPIView):
serializer_class = GroupSerializer
def get_object(self):
queryset = Group.objects.all()
group_id = self.kwargs['group_id']
if group_id is not None:
queryset = queryset.filter(group_id=group_id).first()
return queryset
def get_serializer_context(self): # Passes the URL paramters to the GroupSerializer (serializer doesn't have kwargs).
context = super().get_serializer_context()
context['add_remove'] = self.kwargs['add_remove']
print(self.request.data)
return context
def perform_update(self, serializer):
serializer=GroupSerializer(data=self.request.data, partial=True)
serializer.is_valid(raise_exception=True)
return super().perform_update(serializer)
Within the perform_update function of GroupUpdate, I receive the following: TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use members.set() instead. but I am unsure as to why this error would be raised, considering I was able to associate a user with a group in the create function in pretty much the same way.
This is what a PATCH request would have as the JSON body:
{
"members": [
{
"username": "small_man"
}
]
}
The output of self.request.data is {'members': [{'username': 'small_man'}]}.
You should specify instance of updated object when you create serializer otherwise serializer's save method will call create not update:
def perform_update(self, serializer):
instance = self.get_object()
serializer=GroupSerializer(instance, data=self.request.data, partial=True)
serializer.is_valid(raise_exception=True)
return super().perform_update(serializer)
BTW looks like perform_update is redundant and you can remove it since serializer validation should work without additional modifications.

Django REST: Serializer lookup by UUID

I'm creating this simple shopping API in Django REST.
Internally I'm using IDs for foreign key constraints, while guuids are brought to the outside world.
For the checkout procedure, the user provides a list of article IDs he is willing to purchase. The object in the POST data thus looks as follows:
{
assets: [
{
'product': 'd9d5044d-2284-4d15-aa76-2eee3675035b',
'amount': 4
},
....
]
}
I'm using the following ticket/asset models:
# Ticket
class Ticket(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='tickets', on_delete=models.CASCADE)
# Assets
class Asset(models.Model):
ticket = models.ForeignKey(Ticket, related_name='assets', on_delete=models.CASCADE)
stock_item = models.ForeignKey(Stock, related_name='stock_item', on_delete=models.SET_NULL, null=True)
amount = models.IntegerField(validators=[MinValueValidator(0)])
And the serializers look as follows:
# Asset serializer
class AssetSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ('stock_item', 'amount')
# Ticket serializer
class TicketSerializer(WritableNestedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
assets = AssetSerializer(many=True)
class Meta:
model = Ticket
fields = ('uuid', 'owner', 'assets', )
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
When posting an object of the type specified above, the following error is presented:
{"assets":[{"stock_item": ["Invalid type. Expected PK, received string"]}]}
Which I can't seem to solve, how do I instruct the serializer to use the uuid as the lookup value? I solved a similar problem on view-level earlier by using the lookup_field member, but that doesn't seem to solve it. Any suggestions?
Enter code here
If I have understood you correctly, a SlugRelatedField should be able to find the correct related object.
class AssetSerializer(serializers.ModelSerializer):
ticket = serializers.SlugRelatedField(
read_only=True,
slug_field='uuid',
queryset=Ticket.objects.all() # Might be redundant with read_only=True
)
class Meta:
model = Asset
fields = ('ticket', 'stock_item', 'amount')
Elaborating on #BjornW's comment:
class UUIDRelatedField(serializers.SlugRelatedField):
slug_field = 'uuid'
def __init__(self, **kwargs):
super().__init__(slug_field=self.slug_field, **kwargs)
def to_representation(self, obj):
return getattr(obj, self.slug_field).hex

how to update OneToOneField / ForeignKey using ViewSet? Django

I am currently using restful and serializers to create and update my user.
Somehow I am not able to update some of the fields if the field has to do with OneToOneField / ForeignKey.
in my models.py, my Student is actually connected to the django build in user model which includes the user's email and connected to the school model which has the name of the school
class Student(Model):
user = OneToOneField(settings.AUTH_USER_MODEL, on_delete=CASCADE)
date_of_birth = DateField(blank=True, null=True)
student_name = CharField(max_length=256)
school = ForeignKey(School,
on_delete=CASCADE,
related_name="%(class)ss",
related_query_name="%(class)s",
blank=True,
null=True)
in serializer.py I have
class StudentSerializer(ModelSerializer):
user_email = SerializerMethodField()
school_name = SerializerMethodField()
class Meta:
model = Student
fields = (
'user_email', 'student_name', 'phone', 'school_name')
def get_user_email(self, obj):
return obj.user.email
def get_school_name(self, obj):
return obj.school.school_name
def create(self, validated_data):
return Student.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.user.email = validated_data.get('user_email', instance.user.email)
instance.student_name = validated_data.get('student_name', instance.student_name)
instance.phone = validated_data.get('phone', instance.phone)
instance.school.school_name = validated_data.get('school_name', instance.school.school_name)
instance.save()
return instance
in my view.py update function
class UserViewSet(ViewSet):
queryset = Student.objects.all()
def update(self, request, pk=None):
student = get_object_or_404(self.queryset, pk=pk)
serializer = StudentSerializer(student, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response({'status': True})
return Response({'status': False, 'message': serializer.errors})
I am able to use the API view to pass in json and update the student_name and phone but as for the other two, user_email and school_name I am not able to update it. I don't get any error output when I submit the json though.
I realized the two fields that I am not able to update are because they OneToOneField / ForeignKey.
Can someone please give me a hand what I am missing here or what I can do to check?
Thanks in advance
I think your serializer isn't completed... the field of user and school is instance model, you need specific field in your serializer to implement the instance model, eg: with source='...' argument.
and example:
class VoteSerializer(serializers.ModelSerializer):
# by `username`
user = serializers.CharField(
source='user.username',
read_only=True
)
# by `pk/id`
candidate = serializers.IntegerField(
source='candidate.pk',
read_only=True
)
class Meta:
model = Vote
fields = ('user', 'candidate', 'score')
def create(self, validated_data):
return Vote.objects.create(**validated_data)
and in your case, perhaps is like this;
class StudentSerializer(ModelSerializer):
# by `pk/id` from the user
user = serializers.IntegerField(
source='user.pk',
read_only=True
)
school = serializers.IntegerField(
source='school.pk',
read_only=True
)
Since you are using SerializerMethodField which is readonly field (docs) for user_email and school_name so they won't be available in the validated_data.
Have you check the data you are receiving in validated_data
def update(self, instance, validated_data):
print('++'*22, validated_data)
return instance
The nested seriailzer / model / presentation actually helped me get the work done and pretty helpful.
An example is also provided here.
http://www.django-rest-framework.org/api-guide/serializers/#writing-update-methods-for-nested-representations
the above is continued from
http://www.django-rest-framework.org/api-guide/serializers/#writing-create-methods-for-nested-representations which contained how the nested serializer is being setup in the class and meta's fields

Categories

Resources