How to rename owner to owner_id in ModelSerializer using PrimaryKeyRelatedField? - django

I would like to rename my owner field to owner_id since it is not nested and will only contain the owner's id. I've made some attempts, but receive errors such as {"owner":["This field is required."]}.
Here is my serializers.py:
class UserJobApplicantSerializer(serializers.ModelSerializer):
job_id = serializers.PrimaryKeyRelatedField(source='job', queryset=Job.objects.all())
owner_id = serializers.PrimaryKeyRelatedField(
source='owner',
read_only=True,
default=serializers.CurrentUserDefault()
)
class Meta:
model = JobApplicant
fields = [
'id',
'job_id',
'owner_id',
'timestamp',
]
read_only_fields = ['id',]
The view overrides the perform_create and injects the owner_id into the validated_data:
class UserJobApplicantAPIView(generics.ListCreateAPIView):
lookup_field = 'pk'
serializer_class = UserJobApplicantSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return JobApplicant.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.validated_data['owner_id'] = self.request.user.id
return super(UserJobApplicantAPIView, self).perform_create(serializer)
Model (Job model holds the ManyToManyField with through='JobApplicant'):
class JobApplicant(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
job = models.ForeignKey(Job, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ('owner', 'job')
def __str__(self):
return "{}: {}".format(self.job.display_name, self.owner.email)
What is the correct approach to doing this? Is it possible using PrimaryKeyRelatedField, or would I need to use another type of field (or custom)? I have it working for job_id but job_id is different since it's provided by the user.
I'm using DRF 3.8.2 with Django 1.11.15.

Removing the read_only=True, argument from PrimaryKeyRelatedField solve the main problem :)
It should be,
from django.contrib.auth.models import User # use the AUTH_USER_MODEL here
owner_id = serializers.PrimaryKeyRelatedField(source='owner', queryset=User.objects.all(), default=serializers.CurrentUserDefault())
NOTE : You don't want to override the perform_create() method to pass the user instance to serializer. The CurrentUserDefault() class will manage those things if you are properly logged-in

Related

How to lookup by field inherited from OneToOneField of another model

I am attempting to lookup UserProfile model using a field inherited from the User model. How can I access the username field from the User model in UserProfile view?
I am using Django REST framework 3.9. And from my understanding, using the #property annotation in the Model definition allows you to serialize on that field. And I am using that serialized field as lookup_field in the view.
This is the UserProfile model.
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, null=True, blank=True)
birth_date = models.DateField(null=True, blank=True)
#property
def username(self):
return self.user.username
This is the serializer.
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = [
'user',
'username',
'bio',
'birth_date'
]
This is the view.
class UserProfileAPIView(generics.RetrieveUpdateAPIView):
# permission_classes = [permissions.IsAuthenticated]
serializer_class = UserProfileSerializer
lookup_field = 'username'
def get_queryset(self):
return UserProfile.objects.all()
def get_serializer_context(self, *args, **kwargs):
return {'request': self.request}
This is the API URL.
path('<str:username>/profile/', UserProfileAPIView.as_view(), name='profile'),
I expected the API to return username, bio, birth_date, but I received this error:
Cannot resolve keyword 'username' into field. Choices are: bio, birth_date, id, user, user_id
#property is just a Python calculated field here, which will be available only after the object is available. But to get the objects this cannot be used.
lookup_field requires a DB relation. As I said before, we cannot use the username property for this. It should be lookup_field = 'user__username'.
Add source to the Hyperlink identity field.
URL = HyperlinkIdentityField(
view_name='your_view_name',
source=User,
lookup_field='username'
)

Related Object serializer Django restframework

My question is somewhat related to this one with some differences. I have a model similar to this one:
class Project(models.Model):
project_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
created_by_id = models.ForeignKey('auth.User', related_name='project', on_delete=models.SET_NULL, blank=True, null=True)
created_by = models.CharField(max_length=255, default="unknown")
created = models.DateTimeField(auto_now_add=True)
With the following serializer:
class ProjectSerializer(serializers.ModelSerializer):
created_by = serializers.ReadOnlyField(source='created_by_id.username')
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created')
And corresponding view:
class projectsView(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(created_by_id=self.request.user)
This code behaves like I want but forces information redundancy and does not leverage the underlying relationnal database. I tried to use the info from the linked question to achieve a "write user id on database but return username on "get"" in a flat json without success:
Removing the "created_by" field in the model. Replacing the serializer with:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Which would NOT 100% give me what I want, i.e. replace the user id with the username in a flat json but return something like: {'project_id': <uuid>, 'created_by': <user json object>, 'created': <data>}. But still I get a {'created_by_id': ['This field is required.']} 400 error.
Question: How can I write a user id to a database object from the request.user information to refer to an actual user id but return a simple username in the GET request on the projectsView endpoint without explicitly storing the username in the Model? Or more generally speaking, how can I serialize database objects (Django models) into customer json response by using default serialization DRF features and default DRF views mixins?
Alternate formulation of the question: How can I store an ID reference to another DB record in my model (that can be accessed without it being supplied by the payload) but deserialize a derived information from that object reference at the serializer level such as one specific field of the referenced object?
I would recommend you to use Two different serializers for Get and POST operations. Change your serializers.py as
class ProjectGetSerializer(serializers.ModelSerializer):
created_by_id = serializers.StringRelatedField()
class Meta:
model = Project
fields = '__all__'
class ProjectCreateSerializer(serializers.ModelSerializer):
created_by_id = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), default=serializers.CurrentUserDefault())
def create(self, validated_data):
return Project.objects.create(**validated_data, created_by=validated_data['created_by_id'].username)
class Meta:
model = Project
fields = '__all__'
Also, I reccomend ModelViewSet for API class if you are looking for CRUD operations. Hence the view will be like this,
class projectsView(viewsets.ModelViewSet):
queryset = Project.objects.all()
def get_serializer_class(self):
if self.action == 'create':
return ProjectCreateSerializer
return ProjectGetSerializer
So, the payload to create Project is,
{
}
One thing you should remember, while you trying to create Project user must logged-in
UPDATE - 1
serializer.py
class ProjectCreateSerializer(serializers.ModelSerializer):
created_by_id = serializers.StringRelatedField()
class Meta:
model = Project
fields = '__all__'
def create(self, validated_data):
return Project.objects.create(**validated_data, created_by_id=self.context['request'].user)
views.py
class projectsView(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectCreateSerializer
The error is in the write_only field options. The required parameter default value is set to True while the intent is to not make it required if we take a look at the model. Here in the view, I use the perform_create as post processing to save on the Model DB representation. Since required default value is True at the creation level, the first .save() to the DB fails. Since this is purely internal logic, the required is not necessary. So simply adding the required=False option on the PrimaryKeyRelatedField does the job:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True, required=False)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Enforcing the required=True at the Model level as well would require to override the .save function of the serializer if I insist on playing with the logic purely at the serializer level for deserialization. There might be a way to get the user ref within the serializer as well to keep the views implementation even more 'default'... This can be done by using the default value from Jerin:
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by',
write_only=True,
required=False,
default=serializers.CurrentUserDefault())
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Now to flaten the json with username only, you need to use a slug field instead of the UserSerializer:
class ProjectSerializer(serializers.ModelSerializer):
created_by = serializers.SlugRelatedField(
queryset=User.objects.all(), slug_field="username")
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True, required=False)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
And then only the username field value of the User Model will show at the create_by json tag on the get payload.
UPDATE - 1
After some more tweaking here is the final version I came up with:
class ProjectSerializer(serializers.ModelSerializer):
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), write_only=True, required=False, default=serializers.CurrentUserDefault())
created_by = serializers.SerializerMethodField('creator')
def creator(self, obj):
return obj.created_by_id.username
class Meta:
model = Project
fields = ('project_id', 'created_by_id', 'created_by', 'created')

Django REST Framework - queryset filter only for authenticated user

I'm new to Django REST Framework. I think I have gone messed up in it as sometimes it feels like DRF is easy to understand but later it got messed up.
I have a contacts application.
contacts/models.py
class Contact(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
date_of_birth = models.DateField(blank=True, null=True)
class ContactEmail(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
email = models.EmailField()
class ContactPhoneNumber(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
phone = models.CharField(max_length=100)
Each contact is related some authenticated user.
contacts/serializers.py
class ContactPhoneNumberSerializer(serializers.ModelSerializer):
class Meta:
model = ContactPhoneNumber
fields = ('id', 'phone')
class ContactEmailSerializer(serializers.ModelSerializer):
class Meta:
model = ContactEmail
fields = ('id', 'email')
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ContactPhoneNumberSerializer(source='contactphonenumber_set', many=True)
emails = ContactEmailSerializer(source='contactemail_set', many=True)
class Meta:
model = Contact
fields = ('url', 'id', 'first_name', 'last_name', 'date_of_birth',
'phone_numbers', 'emails')
and contacts/views.py
class ContactViewSet(viewsets.ModelViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)
as far till here, I followed the doc of Django REST Framework but it displays all contacts instead of showing only user's contacts.
To achieve that, I added get_queryset()
class ContactViewSet(viewsets.ModelViewSet):
# queryset = Contact.objects.all()
...
def get_queryset(self):
return Contact.objects.filter(user=self.request.user)
...
But it started giving error as
assert queryset is not None, '`base_name` argument not specified, and could ' \
AssertionError: `base_name` argument not specified, and could not automatically determine the name from the viewset, as it does not have a `.queryset` attribute.
after doing some research, I found to add third parameter to router
app/urls.py
router = routers.DefaultRouter()
router.register(r'contacts', ContactViewSet, 'contacts') # added 'contacts' here
urlpatterns = [
path('api/', include(router.urls))
]
But instead of solving the issue, it generated a new error
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked
relationship using view name "contact-detail".
You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
again after doing some research, found solution to define url in searializers.py. So, I updated my contacts/serializers.py
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ...
emails = ...
url = serializers.HyperlinkedRelatedField(
view_name='contacts:detail'
)
class Meta:
model = Contact
fields = ('url', 'id', 'first_name', 'last_name', 'date_of_birth',
'phone_numbers', 'emails')
But now, it has started giving a new error
'Relational field must provide a `queryset` argument, '
AssertionError: Relational field must provide a `queryset` argument, override `get_queryset`, or set read_only=`True`.
Is'nt there some simple way just to filter objects based on request.user as my application is user based and every authenticated user can view/edit/delete his own data only.
Also, is there any way to get this issue resolved as I'm confused here which Relational field it is talking about?
this solved my issue
set read_only=True to url
url = serializers.HyperlinkedRelatedField(
view_name='contacts:detail',
read_only=True
)
I havent worked with ModelViewSet.
But in a regular APIView, you can do this:
class SingleProject(APIView):
def get(self, request):
project_id = request.query_params.get('id')
ans = Project.objects.filter(id=project_id)
serializer = EagerGetProjectSerializer(qs, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
so maybe something like this:
class ContactViewSet(viewsets.ModelViewSet):
queryset = Contact.objects.filter(user=self.request.user)
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)

django rest framework : nested model get not working. 'str' object has no attribute 'values'

I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
Saving is working perfectly. But now it is showing the following error when I call the GET method.
AttributeError at /api/v1/customer 'str' object has no attribute 'values'
Request Method: GET
bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
bcusomer/serializers.py
class CustomerDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = BCustomer
fields = ('city', 'phone')
class CustomerSerializer(serializers.ModelSerializer):
customer_details = CustomerDetailsSerializer()
class Meta:
model = get_user_model()
fields = ('id','first_name', 'email', 'customer_details')
def create(self, validated_data):
request = self.context.get('request')
customer_details_data = validated_data.pop('customer_details')
customer_user = get_user_model().objects.create(**validated_data)
BCustomer.objects.create(customer=customer_user, user=request.user, **customer_details_data)
customer_user.customer_details = customer_details_data
return customer_user
class CustomerListSerializer(serializers.ModelSerializer):
model = get_user_model()
fields = '__all__'
class Meta:
model = get_user_model()
fields = '__all__'
bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
def get_queryset(self):
queryset = BCustomer.objects.all()
return queryset
def get_serializer_class(self):
if self.action == 'list' or self.action == 'retrieve':
return CustomerListSerializer
return CustomerSerializer
bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
Data post parameter format
{
"first_name":"Myname",
"email":"testemail#gmail.com",
"customer_details": {
"city":"citys",
"phone":"04722874567",
}
}
You should remove model and fields from CustomListSerializer
class CustomerListSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = '__all__'
customer_details = CustomerDetailsSerializer()
You need to set the source argument to point to the user model's customer. Most probably:
customer_details = CustomerDetailsSerializer(source='customer')
(or maybe source='bcustomer', not sure if it reversed the field name or class name).
On a side not, you should not need the ListSerializer at all. The list method will call the serializer with the many=True argument on CustomerSerializer which will create the ListSerializer appropriately.

Django REST Framework (DRF): Set current user id as field value

I have model NewsModel and 2 serializers for him:
models.py
class NewsModel(models.Model):
title = models.CharField('Заголовок', max_length=255, help_text='Максимальная длина - 255 символов')
announce = models.TextField('Анонс', help_text='Краткий анонс новости')
author = models.ForeignKey(settings.AUTH_USER_MODEL, help_text='Автор новости', related_name='news')
full_text = models.TextField('Полный текст новости', help_text='Полный текст новости')
pub_date = models.DateTimeField('Дата публикации', auto_now_add=True, default=timezone.now, help_text='Дата публикации')
def comments_count(self):
return NewsComment.objects.filter(news=self.id).count()
def get_author_full_name(self):
return self.author.get_full_name()
class Meta:
db_table = 'news'
ordering = ('-pub_date',)
serilizers.py:
from rest_framework import serializers
from .models import NewsModel
from extuser.serializers import UserMiniSerializer
class NewsReadSerializer(serializers.ModelSerializer):
author = UserMiniSerializer()
class Meta:
model = NewsModel
fields = ('id', 'title', 'announce', 'comments_count', 'reviews', 'author_name')
def get_author_full_name(self, obj):
return obj.get_author_full_name()
class NewsWriteSerializer(serializers.ModelSerializer):
def validate_author(self, value):
value = self.request.user.id
return value
class Meta:
model = NewsModel
I select serializers in the api.py:
class NewsList(ListCreateAPIView):
queryset = NewsModel.objects.order_by('-pub_date')
def get_serializer_class(self, *args, **kwargs):
if self.request.method == 'GET':
return NewsReadSerializer
return NewsWriteSerializer
class Meta:
model = NewsModel
But when I will create NewsModel item, I see Error 400: Bad request [{'author': 'This field is required'}]
How I can set current user id as NewsItem.author value on creating new item?
I don't think you're using the serializer properly. A better practice to set request related data is to override perform_create in your view:
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def perform_update(self, serializer):
serializer.save(author=self.request.user)
and then set your author serializer to read-only:
author = UserMiniSerializer(read_only=True)
this way you can simply use one single NewsSerializer for both read and write actions.
In new DRF you can write
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
See http://www.django-rest-framework.org/api-guide/validators/#currentuserdefault
In DRF version prior 3 field must be declader with allow_null=True and default=None. DRF don't run checking fields without this params. Result code:
class NewsReadSerializer(serializers.ModelSerializer):
"""
Serializer only for reading.
author field serialized with other custom serializer
"""
author = UserMiniSerializer()
class Meta:
model = NewsModel
fields = ('id', 'title', 'announce', 'comments_count', 'reviews', 'author', 'pub_date',)
class NewsWriteSerializer(serializers.ModelSerializer):
"""
Serializer for creating and updating records.
author here is the instance of PrimaryKeyRelatedField, linked to all users
"""
author = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), # Or User.objects.filter(active=True)
required=False,
allow_null=True,
default=None
)
# Get the current user from request context
def validate_author(self, value):
return self.context['request'].user
class Meta:
model = NewsModel
fields = ('title', 'announce', 'full_text', 'author',)
I would try something like this:
your models.py
class NewsModel(models.Model):
title = models.CharField(
'Заголовок', max_length=255,
help_text='Максимальная длина - 255 символов')
announce = models.TextField('Анонс',
help_text='Краткий анонс новости')
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
help_text='Автор новости', related_name='news')
full_text = models.TextField(
'Полный текст новости',
help_text='Полный текст новости')
pub_date = models.DateTimeField(
'Дата публикации', auto_now_add=True,
default=timezone.now, help_text='Дата публикации')
def comments_count(self):
return NewsComment.objects.filter(news=self.id).count()
def get_author_full_name(self):
return self.author.get_full_name()
class Meta:
db_table = 'news'
ordering = ('-pub_date',)
serializers.py
(ref.: http://www.django-rest-framework.org/api-guide/validators/#currentuserdefault)
from <yourapp>.models import NewsModel
from rest_framework import serializers
class NewsModelSerializer(serializers.ModelSerializer):
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = NewsModel
Also you should set settings.py to something like this:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',)
}