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!
Related
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']
I am using the Django Rest Framework. I have two models as shown below:
class Following(models.Model):
target = models.ForeignKey('User', related_name='followers', on_delete=models.CASCADE, null=True)
follower = models.ForeignKey('User', related_name='targets', on_delete=models.CASCADE, null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{} is followed by {}'.format(self.target, self.follower)
class User(AbstractBaseUser):
username = models.CharField(max_length=15, unique=True)
email = models.EmailField(max_length=100, unique=True)
What I'd like to have an API that will return all the followers the user has. However, I don't want it to be returned in the Following model format, I'd like for the followers to be returned as users in this format:
[
{
"username": Bob,
"email": example#example.com
},
{
"username": Charlie,
"email": example#example.com
}
]
Right now, I have something like this:
class FollowingAPIView(ListAPIView):
serializer_class = FollowingSerializer
def get_queryset(self):
return Following.objects.filter(target=3)
class FollowingSerializer(serializers.ModelSerializer):
class Meta:
model = Following
fields = ('follower', 'target')
This only shows the follower and target I don't want this. I want the actual data from User Also, I put target=3 as a placeholder just to test.
you should use a serializer for the User not following:
class UserSerializer(serializers.ModelSerializer):
is_following = serializer.IntegerField()
class Meta:
model = User
fields = ('username', 'email', 'is_following')
also, change your queryset in view as below:
from django.db import models
from rest_framework.generics import get_object_or_404
class FollowingAPIView(ListAPIView):
serializer_class = FollowingSerializer
### so add this method to get the requested user based on the username in the url
def get_requested_user(self):
##get the user based on the user name in url
filter_kwargs = {'username': self.kwargs['username']}
obj = get_object_or_404(User.objects.all(), **filter_kwargs)
return obj
def get_queryset(self):
requested_user = self.get_requested_user()
return User.objects.filter(targets__target=requested_user)\
.annotate(is_following=models.Count('followers', filter=models.Q(followers__follower=requested_user), distinct=True))
if you are using Django<2.0, your get_queryset should be like:
def get_queryset(self):
requested_user = self.get_requested_user()
return User.objects.filter(targets__target=requested_user)\
.annotate(is_following=models.Count(models.Case(models.When(models.Q(followers__follower=requested_user), 1))))
because you want to return a list of users, not Following instances. use following only to filter( targets__ in the filter above) the users that their target in Following is the currently authenticated user(at least in one of its targets).
updated
also, change your url to something like this:
path('/api/followers/<username>/', FollowingAPIView.as_view(), name='get_user_followers')
I have am implementing a follower and followers system in my drf api.
My relationship model and serializer:
models.py
class Relationship(models.Model):
user = models.ForeignKey(User, related_name="primary_user", null=True, blank=True)
related_user = models.ForeignKey(User, related_name="related_user", null=True, blank=True)
incoming_status = models.CharField(max_length=40, choices=RELATIONSHIP_CHOICE, default=RELATIONSHIP_CHOICE[0])
def __str__(self):
return self.user.username + " " + self.incoming_status + " " + self.related_user.username
serializers.py
class RelationshipSerializer(serializers.ModelSerializer):
outgoing_status = serializers.SerializerMethodField()
class Meta:
model = models.Relationship
fields = (
'user',
'related_user',
'incoming_status',
'outgoing_status',
)
def get_outgoing_status(self, obj):
related_user = obj.related_user
user = obj.user
try:
query = models.Relationship.objects.get(user=related_user, related_user=user)
user_id = query.incoming_status
except models.Relationship.DoesNotExist:
user_id = "none"
return user_id
views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = models.User.objects.all()
serializer_class = serializers.UserSerializer
#detail_route(methods=['get'], url_path='relationship/(?P<related_pk>\d+)')
def relationship(self, request, pk, related_pk=None):
user = self.get_object()
query = models.Relationship.objects.filter(user=user)&\
models.Relationship.objects.filter(related_user__pk=related_pk)
serializer = serializers.RelationshipSerializer(query, many=True)
return Response(serializer.data)
Incoming status is your relationship to the user and outgoing status is the users relationship to you to so for example this can return
[
{
"user": 2,
"related_user": 1,
"incoming_status": "follows",
"outgoing_status": "none"
}
]
This means you follow the user and the user doesn't follow you back
When you follow a user my code works fine because it returns "incoming_status": "follows" and it then checks for the outgoing status in the serializers. However when the:
query = models.Relationship.objects.filter(user=user) &
models.Relationship.objects.filter(related_user__pk=related_pk)
query in views.py returns null the meaning that the incoming status is none the serializer outputs [] because the query is empty. How do I make incoming_status = "none" if the query is empty?
Thanks in advance
I've 2 models:-
class Users(models.Model):
first_name = models.CharField(max_length=255)
middle_name = models.CharField(max_length=255)
class UserAddress(models.Model):
line1 = models.CharField(max_length=255)
country = models.CharField(max_length=255)
user = models.ForeignKey(Users)
The user model & user address model. Following are the 2 serializers.
class UserAddressSerializer(ModelSerializer):
class Meta:
model = UserAddress
exclude = ('id', 'user')
class UserSerializer(ModelSerializer):
address = UserAddressSerializer(many=True)
class Meta:
model = Users
fields = '__all__'
def create(self, validated_data):
address = validated_data.pop('address', [])
user = Users.objects.create(**validated_data)
for ad in address:
UserAddress.objects.create(user=user, **ad)
return user
The data I receive from the client is
{
"first_name": "string",
"last_name": "string",
"address": [{
"line1": "asd",
"country": "asd",
}],
}
This is how I create a new user and its corresponding address.
class UserCreate(GenericAPIView):
serializer_class = UserSerializer
def post(self, request, *args, **kwargs):
data = request.data
serializer = UserSerializer(data=data)
if not serializer.is_valid():
return
user = serializer.save()
response = {
'user_id': user.uuid
}
return
Now, upon getting the user details back, I receive an error saying
AttributeError: Got AttributeError when attempting to get a value for field `address` on serializer `UserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Users` instance.
Original exception text was: 'Users' object has no attribute 'address'.
This is how I get the details of the user, including the address.
class UserDetails(GenericAPIView):
queryset = Users.objects.all()
serializer_class = UserSerializer
lookup_field = 'uuid'
def get(self, request, uuid, *args, **kwargs):
user = Users.get_user(uuid)
if not user:
return
serializer = UserSerializer(instance=user)
return
I'd read this example of nested relationship, and am doing exactly the same way. why is the error coming up?
Also, can this code be shorten up more (in a nicer clean way) using some DRF mixins? If yes, then how?
I think the most simple solution for your case is: in model UserAddress add related_name='address'
class UserAddress(models.Model):
line1 = models.CharField(max_length=255)
country = models.CharField(max_length=255)
user = models.ForeignKey(Users, related_name='address')
# ^^^^^^^^^^^^^^^
or you can add sourse property in serializer:
class UserSerializer(ModelSerializer):
address = UserAddressSerializer(source='useraddress_set', many=True)
Serializer try to find attribute 'address' in the model User, but by default it is modelname underscore set (useraddress_set in your case), and you try other name, so you can set in the model or specify by source.
in the example you can look on models and find the related_name='tracks'
When i try to add a Car for my user this is what's happening
"username": [
"A user with that username already exists."]
This is my models I'm using Django auth User model
There are my models
class Car(models.Model):
"""
CAR Table with a ForeingKey from USER Table
"""
user = models.ForeignKey(User, related_name='cars')
model = models.CharField(max_length=50)
color = models.CharField(max_length=20)
year = models.IntegerField()
seats = models.IntegerField()
def __unicode__(self):
return u'%d: %s: %s: %d' % (self.id, self.model, self.color, self.year)
My Serializers
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = (
'model',
'color',
'seats'
)
class AddCarSerializer(serializers.ModelSerializer):
car = CarSerializer()
class Meta:
model = User
fields = (
'username',
'car'
)
this is my View
class AddCarForUser(APIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def put(self, request):
car = AddCarSerializer(data = request.data)
car.is_valid(raise_exception = True)
car.save()
return Response(status=status.HTTP_201_CREATED)
And this is what i'm sending in a Put Request
{
"username": "root",
"car": {
"model": "Seat Ibiza",
"color": "Verde",
"seats": "4"
}}
Your code doesn't works because you use serializer.ModelSerializer and the username attribute of theUser class must be unique, that why It can't validate it. To overcome this, follow these steps.
Update your serializer AddCarSerializer. We add a custom field username to handle username passed without a unique, just a simple CharField. And we add create function because Nested Serializer can't handle creation or update out the box:
class AddCarSerializer(serializers.Serializer):
# add username custom field
username = serializers.CharField()
car = CarSerializer()
class Meta():
fields = (
'username',
'car'
)
def create(self, validated_data):
""" Add car to an user
"""
tmp_car = validated_data.pop('car')
user = User.objects.get(username=validated_data['username'])
car = Car.objects.create(
user=user,
model=tmp_car['model'],
color=tmp_car['color'],
seats=tmp_car['seats'],
)
return car
update your view :
class AddCarForUser(APIView):
def put(self, request):
serializer = AddCarSerializer(data = request.data)
if serializer.is_valid():
serializer.save()
return Response(status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST)
update your Car model, because year attribute doesn't exist when we add a car to an user then, add null=True :
class Car(models.Model):
user = models.ForeignKey(User, related_name='cars')
model = models.CharField(max_length=50)
color = models.CharField(max_length=20)
year = models.IntegerField(null=True)
seats = models.IntegerField()
And it should work. Don't forget to handle error like if the username passed doesn't exist.