everyone, I am absolutely a beginner in DjangoRestFramework. I have confusion to deal with relationships in DRF. How do I save foreign keys data using APIView?
models
class User(AbstractUser):
email = models.EmailField(max_length=255, unique=True)
is_client = models.BooleanField(default=False)
is_professional = models.BooleanField(default=False)
class Client(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='client')
##
class Professionals(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='professional')
##
class HireProfessional(models.Model):
client = models.ForeignKey(Client, related_name='user', on_delete=models.CASCADE)
professional = models.ForeignKey(Professionals, on_delete=models.CASCADE, related_name="professsional")
hire_start_date_time = models.DateTimeField(default=timezone.now)
Serializers
class ProfessionalSerializer(serializers.ModelSerializer):
profilePicture = serializers.ImageField(allow_empty_file=True, use_url='professional/profiles', required=False)
skill = SkillSerializer(many=True,read_only=True)
class Meta:
model = Professionals
fields = fields = ['first_name', 'last_name', 'profilePicture', 'profession', 'phone_number', 'experience', 'skill', 'charge_fee', 'about_me']
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ['user_id', 'first_name', 'last_name', 'phone_number', 'profilePicture']
class UserSerializer(serializers.ModelSerializer):
client = ClientSerializer()
professional = ProfessionalSerializer()
class Meta:
model = User
fields = ('email', 'username', 'is_client', 'is_professional', 'client', 'professional')
class HireProfessionalSerializer(serializers.ModelSerializer):
client = ClientSerializer()
professional = professionalSerializer()
class Meta:
model = HireProfessional
fields = ['id','client', 'professional', 'hire_start_date_time']
views ##Edited
class HireProfessionalCreateApp(APIView):
permission_classes = (IsAuthenticated, IsClientUser,)
def current_user(self):
user = self.request.user.client
return user
def post(self, request, username, format=None):
try:
professional = Professionals.objects.get(user__username=username)
# print('hello', professional.user_id)
data = request.data
data['client'] = self.current_user()
data['professional'] = professional.user_id
serializer = HireProfessionalSerializer(data=data)
data = {}
if serializer.is_valid():
hire = serializer.save()
hire.save()
return JsonResponse ({
"message":"professional hired success.",
# "remaning_time":remaning_datetime,
"success" : True,
"result" : serializer.data,
"status" : status.HTTP_201_CREATED
})
else:
data = serializer.errors
print(data)
return Response(serializer.data)
except Professionals.DoesNotExist:
return JsonResponse ({"status":status.HTTP_404_NOT_FOUND, 'message':'professional does not exists'})
This is a hiring app.
Client able to hire to professional
client=logged in user
professional=passed id or username through URL
like: path('hire-professional/<professional id or username>', views)
Does anybody have any idea? how to solve it.
Consider using a ModelViewSet instead of an APIView since you're directly modifying the HireProfessional model. Additionally, the model uses ForeignKey fields for Client and Professional, you do not need to include their respective serializers in HireProfessionalSerializer.
Implementing just the ModelViewSet (and adding it to the urls.py using a router) will mean that a user can select the client & professional themself, which means we're not done yet. It is recommended to use a router in DRF instead of adding all the views to urls.py manually.
You can use the ModelViewSet to override the perform_create and perform_update functions in which you autofill serializer fields. :
default create function
def perform_create(self, serializer):
serializer.save()
autofill example
def perform_create(self, serializer):
# client is derived from logged in user
client = Client.objects.get(user=self.request.user)
# Autofill FK Client.
serializer.save(client=client)
Now the client is autofilled, but the professional isn't yet. If you want to also autofill the professional you will have to look into using a nested router since you want to retrieve the PK(id) from the URL. If you do so your URL would look something like this:
url='/professionals/{professionals_pk}/hire/'
I hope this gives you an idea of where to start. If you have any questions do let me know.
Related
I have imported User model and customized it a/c to my need and make OneToOne Relation with UserProfileModel Model. While retrieving data I got this error.
"The serializer field might be named incorrectly and not match any attribute or key on the AnonymousUser instance.
Original exception text was: 'AnonymousUser' object has no attribute 'gender'."
My Model is :
class UserProfileModel(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='userprofilemodel')
gender = models.CharField(max_length=20)
locality = models.CharField(max_length=70)
city = models.CharField(max_length=70)
address = models.TextField(max_length=300)
pin = models.IntegerField()
state = models.CharField(max_length=70)
profile_image = models.FileField(upload_to='user_profile_image', blank=True)
My Serializer looks like:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model= User
fields = ['id', 'name' , 'email','mobile',]
class UserProfileModelSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
model= UserProfileModel
fields = ['user','gender' , 'locality','city','address','pin','state','profile_image', ]
My view looks like:
class UserProfileDataView(APIView):
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
I want to retrieve profile data of the logged user using UserProfileModel Model
Your first issue in that you are passing a User instance to the UserProfileModelSerializer, which is expecting a UserProfileModel instance. To fix this you need to change:
serializer = UserProfileModelSerializer(request.user)
to
serializer = UserProfileModelSerializer(request.user.userprofilemodel)
where userprofilemodel is the related_name you have set on the user field in your UserProfileModel.
Second issue is, as Mohamed Beltagy said, you're allowing anyone to access the view, including unauthenticated users. Django rest framework has a built in mixin that you can use to restrict access to authenticated users only (https://www.django-rest-framework.org/api-guide/permissions/#isauthenticated).
from rest_framework.permissions import IsAuthenticated
class UserProfileDataView(APIView):
permission_classes = [IsAuthenticated]
the problem here is you are passing an anonymous user which has no profile ( you permit non-authenticated users access this view)
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
if request.user.is_authenticated:
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_401_UNAUTHORIZED)
I've been struggling to understand how one would update a M2M-field in the django rest framework that is between a custom user and some random field. I should mention I'm using Djoser as authentication.
Lets say I have a custom user
Models:
class CustomUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
paying_user = models.BooleanField(default=False)
subscribed_companies = models.ManyToManyField('myapp.Company')
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserAccountManager()
def __str__(self):
return f"{self.email}' account"
class Company(models.Model):
name = models.CharField(max_length=150)
def __str__(self):
return self.name
class Meta:
ordering = ['name']
My serializers
Imports - serializers.py:
from django.contrib.auth import get_user_model
from djoser.serializers import UserCreateSerializer
from rest_framework import serializers
from apartments.models.company_model import Company
User = get_user_model()
class UserCreateSerializer(UserCreateSerializer):
class Meta(UserCreateSerializer.Meta):
model = User
fields = ('email','password', 'paying_user', 'subscribed_companies')
class UserCompanyListSerializer(serializers.ModelSerializer):
#Is this a reasonable way to serialize a M2M-field?
subscribed_company_ids = serializers.PrimaryKeyRelatedField(many=True, read_only=False,
queryset=Company.objects.all(), source='subscribed_companies')
class Meta:
model = User
fields = [
'subscribed_company_ids'
]
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('name',)
As you can see, I've attached a M2M-field on the custom user itself, instead of using a OneToOne-field where I store custom data. I'm not sure this is the best way to do it.
The idea is that a user should be able to, on the front end, have a list of companies it wants to subscribe to once they are logged in. That means I'll have many users that can subscribe to many companies.
Where I'm really doubting myself is how I handled the class based views.
Since I can retrieve the ID from request.user.id and since I want to replace the entire list of companies, I don't need the PK which identifies a specific company.
Therefore, in the put method, I removed the PK parameter. This works.
So my question is - Is there a more clean way to do it? Looking at posts at stackoverflow I couldn't find a decent answer that involved authentication. Am I approaching it wrong?
class UserCompanies(APIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get(self, request):
user_id = request.user.id
instance = CustomUser.objects.get(id=user_id)
serializer = UserCompanyListSerializer(instance)
return Response(serializer.data)
def put(self, request, format=None):
user_id = request.user.id
instance = CustomUser.objects.get(id=user_id)
serializer = UserCompanyListSerializer(instance, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
How a GET request response would look to localhost:8000/usercompanies/:
{
"subscribed_company_ids": [
2,
1,
3
]
}
How a PUT request response would look to localhost:8000/usercompanies/:
{
"subscribed_company_ids": [
2,
1,
3,
5,
4,
]
}
Feedback would be much appreciated, I'm a total DRF newbie.
I am trying to create a simple API to get a user register.
I am using the default User table for authentication purpose, created another table called "phone" with one to one relation with User.
I am trying to add "phone" field just above the password. (I hope the image attached is visible).
**
Serializer.py
class UserRegisterSerializer(serializers.ModelSerializer):
class Meta:
model = UserDetailsModel
fields = ('phone', 'user')
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(max_length=68, min_length=6, write_only=True)
class Meta:
model = User
fields = ('username','first_name', 'last_name','email','password')
read_only_fields = ('id',)
**
models.py<<
**
class UserDetailsModel(models.Model):
phone = models.IntegerField()
balance = models.DecimalField(max_digits=10, decimal_places=2, default=0)
user = models.OneToOneField(get_user_model(),primary_key='email' , on_delete=models.CASCADE)
def __str__(self):
return str(self.user)
**
views.py
**
class RegisterView(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request):
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
return Response(user_data,status=status.HTTP_201_CREATED)
class DetailsRegisterView(generics.GenericAPIView):
serializer_class = UserRegisterSerializer
def post(self, request):
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
return Response(user_data,status=status.HTTP_201_CREATED)
**
urls
**
urlpatterns = [
path('',RegisterView.as_view()),
path('details', DetailsRegisterView.as_view())
]
**
You probably can use source in a serializer with a FK
class RegisterSerializer(...)
...
phone = serializers.CharField(..., source='userdetails.phone')
see also : the doc
I have some doubt in create case, in a update case this code work fine.
see also : How to serialize a relation OneToOne in Django with Rest Framework?
and an other way to resolve your issue : nested serializer
Updated code:
serializers>
from django.contrib.auth.models import User
from django.http import JsonResponse
from rest_framework import serializers
from .models import UserDetailsModel
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username','first_name', 'last_name','email','password')
read_only_fields = ('id',)
class UserRegisterSerializer(serializers.ModelSerializer):
user = RegisterSerializer(required=True)
class Meta:
model = UserDetailsModel
fields = ('phone','user')
def create(self, validated_data):
user_data = validated_data.pop('user')
user = RegisterSerializer.create(RegisterSerializer(), validated_data=user_data)
data, created = UserDetailsModel.objects.update_or_create(user=user,
phone=validated_data.pop('phone'))
return data
class DetailView(serializers.ModelSerializer):
user = RegisterSerializer(read_only=True)
class Meta:
model = UserDetailsModel
fields = ('user','phone')
Remaining code stays the same.
I'm trying to make an image upload through a REST API from a mobile client. I've managed to implement it using an multipart request to the REST endpoint, but when I try to update the image, the request is not handled correctly because of the constraints on the OneToOneField.
This is how I implemented the API:
models.py
class Hotel(models.Model):
name = models.CharField(max_length=500, null=False, blank=False)
address = models.CharField(max_length=500, null=False, blank=False)
rating = models.FloatField()
owner = models.CharField(max_length=200, null=False)
class Meta:
ordering = ['name']
class HotelPhoto(models.Model):
photo = models.ImageField(upload_to='hotel_photos', null=True)
hotel = models.OneToOneField(Hotel, on_delete=models.CASCADE, primary_key=True)
views.py
class HotelPhotoUpload(APIView):
parser_classes = [FormParser, MultiPartParser]
def post(self, request):
photo_serializer = HotelPhotoSerializer(data=request.data,
context={'request': request})
if photo_serializer.is_valid():
photo_serializer.save()
return Response(photo_serializer.data,
status=status.HTTP_201_CREATED)
else:
logger.error(f'Error uploading image: {photo_serializer.errors}')
return Response(photo_serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
serializers.py
class HotelSerializer(serializers.HyperlinkedModelSerializer):
photo = serializers.ImageField(source='hotelphoto.photo', read_only=True)
class Meta:
model = Hotel
fields = ['url', 'id', 'name', 'address', 'rating', 'owner', 'photo']
class HotelPhotoSerializer(serializers.ModelSerializer):
photo_url = serializers.SerializerMethodField()
class Meta:
model = HotelPhoto
fields = ['hotel', 'photo', 'photo_url']
def get_photo_url(self, obj):
return self.context['request'].build_absolute_uri(obj.photo.url)
This is the error I'm getting:
Error uploading image: {'hotel': [ErrorDetail(string='hotel photo with this hotel already exists.', code='unique')]}
Bad Request: /hotels/photo/upload/
I understand this is due to the constraint on the OneToOneField since I've have already uploaded a photo, but how should I do the request in order to just update the HotelPhoto.photo field?
What I've tried
Implementing a put method on the HotelPhotoUpload view with partial=True on the serializer, but it gave the same error.
I thought about overwriting the validate method on the serializer, but I don't know if I need to validate anything on the photo itself. I was hoping the framework would handle this for me.
Thought about merging the HotelPhoto and Hotel models, but that would require a big refactor of other code.
EDIT:
I'm currently using django 3.0.2.
Following the answer by neferpitou, I've managed to get it working after these minor changes:
serializers.py
# Didn't change the HotelSerializer
class HotelPhotoSerializer(serializers.ModelSerializer):
hotel = serializers.PrimaryKeyRelatedField(
many=False,
queryset=Hotel.objects.all())
class Meta:
model = HotelPhoto
fields = ['hotel', 'photo']
def create(self, validated_data):
# Instead of creating a new HotelPhoto instance
# changed the photo field from the Hotel instance
hotel = validated_data.get('hotel')
photo = validated_data.get('photo')
hotel.hotelphoto.photo.save(photo.name, photo)
hotel.save()
return hotel.hotelphoto
views.py
class HotelPhotoUpload(APIView):
parser_classes = [FormParser, MultiPartParser]
def post(self, request):
# I'm already sending the hotel id on the POST request
photo_serializer = HotelPhotoSerializer(data=request.data)
if photo_serializer.is_valid():
photo_serializer.save()
return Response(photo_serializer.data,
status=status.HTTP_201_CREATED)
else:
logger.error(f'Error uploading image: {photo_serializer.errors}')
return Response(photo_serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
One thing that I forgot to mention is that I'm always sending a request (either POST or PUT) to the hotels endpoint (which uses the HotelSerializer) before uploading a photo. So I'm not expecting to have problems on the photo/upload endpoint due to an inexistent hotel.
mobile client
Unfortunaly can't post the Multipart POST request content because it's huge. But here is the client method implementation using Retrofit 2.5.0.
// Sends the hotel id and the entire content of the photo file.
#Multipart
#POST(UPLOAD_ENDPOINT)
fun uploadPhoto(#Part("hotel") hotelId: RequestBody,
#Part photo: MultipartBody.Part) : Call<UploadResult>
companion object {
const val UPLOAD_ENDPOINT = "hotels/photo/upload/"
}
Foreignkey and OneToOneField can be serialized in the same manner.
Here is your
views.py
class HotelPhotoUpload(APIView):
# parser_classes = [FormParser, MultiPartParser]
def post(self, request):
hotel = Hotel.objects.get(name=request.data.get('hotel'))
request.data['hotel'] = hotel.id
photo_serializer = HotelPhotoSerializer(data=request.data)
# print(photo_serializer)
if photo_serializer.is_valid():
photo_serializer.save()
return Response(photo_serializer.data,
status=status.HTTP_201_CREATED)
else:
# logger.error(f'Error uploading image: {photo_serializer.errors}')
return Response(photo_serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
serializers.py
class HotelSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Hotel
fields = '__all__'
class HotelPhotoSerializer(serializers.ModelSerializer):
hotel = serializers.PrimaryKeyRelatedField(many=False, queryset=Hotel.objects.all())
class Meta:
model = HotelPhoto
fields = ['hotel', 'photo',]
def create(self, validated_data):
hotel_photo = HotelPhoto.objects.create(**validated_data)
hotel_photo.save()
return hotel_photo
I am confused why there are extra fields in your HotelSerializer, so I have trimmed it down. If you have specific use case for those feel free to modify in your code. And there are no primary key in your Hotel model, so it will create id field by default and I am assuming every hotel name in unique.
Postman request:
Hotel Data From Admin Section:
In my models an User is related with its Profile, which has Companies. In order to serialize them I want that if the user who makes the request is_staff, then the serializer must return all the companies, not only the ones s/he has through the relationship in the model.
What's the proper way to...:
1) Check if the user is staff.
2) Return all the companies if user is staff or return the companies of the profile related with the user.
I guess the best way to deal with this would be check in BProfileSerializerRelated if the user is staff and then add something like this:
company = serializers.SerializerMethodField('get_companies')
def get_companies(self, obj):
companies = Company.objects.all()
serializer = CompanySerializer(instance=companies, many=True)
return serializer.data
My current code doesn't take into account if the user is staff so return just the companies related with that user:
models.py
class BUser(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=40, unique=True)
first_name = models.CharField(max_length=40)
last_name = models.CharField(max_length=40)
class BProfile(models.Model):
user = models.ForeignKey('BUser')
company = models.ForeignKey('BCompany')
groups = models.ManyToManyField(Group)
class BCompany(models.Model):
name = models.CharField(max_length=64)
dealer = models.ForeignKey(BProfile, related_name='companies', blank=True, null=True)
views.py
#api_view(['GET'])
def current_user_detail(request):
serializer = BUserSerializerRelated(request.user)
return Response(serializer.data)
serializers.py
class BUserSerializerRelated(serializers.ModelSerializer):
bprofile_set = BProfileSerializerRelated(many=True)
class Meta:
model = BUser
fields = ('id', 'bprofile_set', 'username', 'email', 'first_name', 'last_name')
class BProfileSerializerRelated(serializers.ModelSerializer):
company = CompanySerializer()
groups = GroupSerializer(many=True)
dealer_companies = CompanySerializer(many=True)
class Meta:
model = BProfile
fields = ('id', 'dealer_companies', 'company', 'groups')
class CompanySerializer(serializers.ModelSerializer):
sites = SiteSerializer(many=True)
services = ServiceSerializer(many=True)
class Meta:
model = Company
fields = ('id', 'dealer','name', 'cif', 'sites', 'services')
Serializer has request object in his context dict. So you can get the current user from there.
def get_companies(self, obj):
user = self.context['request'].user
if user.is_staff:
serializer = CompanySerializer(Company.objects.all(), many=True)
else:
serializer = CompanySerializer(instance=companies, many=True)
return serializer.data
To access request object in serializer you need to pass it context dict while initializing serializer i.e in your views.py
serializer = BUserSerializerRelated(request.user, context={'request': self.request})
then in your serializers.py, you can access request user object like
user = self.context['request'].user