Serializer `validate()` not getting called on `is_valid()` on POST - django-rest-framework

I want to create a non class-based form to facilitate uniform logging in by users across all front-end apps. Currently, it looks like this
My serializer class:
class EmployeeLoginSerializer(serializers.Serializer):
username = serializers.CharField(min_length=6)
password = serializers.CharField(min_length=6)
def validate_credentials(self, attrs):
print('validating')
try:
emp: Employee = Employee.objects.get(username=attrs['username'])
if crypto.verify_password(attrs['password'], emp.password_hash):
return attrs
except Exception:
raise serializers.ValidationError("Incorrect username or password")
raise serializers.ValidationError('Incorrect username or password')
My view class:
class TestView(APIView):
serializer_class = EmployeeLoginSerializer
def get(self, request, *args, **kwargs):
return Response({'Message': 'Get works'})
def post(self, request, *args, **kwargs):
print(request.POST)
serializer = self.serializer_class(data=request.POST)
if serializer.is_valid():
return Response({'Message': 'Credentials were correct'})
My issue is that serializer.is_valid() doesn't seem to be calling on validate automatically. I know I could just call serializer.validate() manually but all the docs and questions on StackOverflow seem to show validate() being called by is_valid() automatically so I get that feeling that that wouldn't be the best practice. Is there something I'm missing?

The is_valid() method will call validate() method of the serializer and validate_FIELD_NAME() methods.
In your code, the validate_credentials() seems a regular class method which won't detect by DRF since the credentials isn't a field on the serializer.

Related

django - pass request.user to ModelForm

i am having a tricky issue. In my views.py i am passing a form in a DetailView. But i am not using a FormMixin. That has the reason that i only want my template to render the form. In the template i use that form to trigger an UpdateView.
class UpdateDetailView(LoginRequiredMixin, DetailView):
model = Balance
def get_context_data(self, **kwargs):
context = super(UpdateDetailView, self).get_context_data(**kwargs)
context['form'] = ManagerForm
return context
def get_queryset(self, *args, **kwargs):
request = self.request
pk = self.kwargs.get('pk')
select = self.kwargs.get('select')
queryset = Balance.objects.filter(pk=pk).filter(owner = request.user).filter(select = select)
return queryset
class BalanceUpdateView(LoginRequiredMixin, UpdateView):
form_class = ManagerForm
model = Balance
def get_success_url(self):
return reverse('url-year', kwargs={'year': self.object.year, 'select': self.object.select})
So far, so good. The issue is that the form in the UpdateDetailView the ChoiceFields are showing select option of other users which one is not supposed to see. Thus i need to override the queryset of the Modelform. Usually i could use the get_form_kwargs method to pass the request.user to the form. But since the UpdateDetailView is not a FormView it doesnt have that (i know, coz i tried desperately). I also tried context['form'] = ManagerForm(initial = {'user': self.request.user}) in the get_context_data method. But i was unable to access user properly with the __init__ method in the forms.py. I got a key error. If i passed it as arg then i get an attribute error. Does anyone have a solution to that problem? Do i need to use a FormMixin?
Thank you for your replies!

I'm trying to add a dynamic queryset to a model-form/formset field from a url kwarg <int:url_kwarg>, no luck so far

Edit: It appears that the problem I'm experiencing is directly related to the formset_factory() call. For some reason or other, I have been unable to access the kwargs from the view if I pass the form through the function.
I'm building a web app that utilizes high normalization in the data-structure. There are many many-to-many and one-to-many relationships to prevent excess null records and database-bloat. I want to add more entries to a model while excluding existing entries from the model.choice field.
my code looks like this:
the form:
class ExtraAddForm(forms.ModelForm):
def __init__(self, url_kwarg, *args, **kwargs):
super(ExtraAddForm, self).__init__(self, *args, **kwargs)
list_to_exclude = []
query_target = models.Model.objects.get(fk_id=url_kwarg)
for object in query_target:
list_to_exclude.append(object.fk_id.id)
new_queryset = models.Model.objects.exclude(fk_id__in=list_to_exclude)
self.fields['fk_id'].queryset= new_queryset
class Meta:
model = models.Model
fields= ['fk_id','field_b'}
the view:
class AddOjbectsView(FormView):
formset = formset_factory(ExtraAddForm(url_kwarg), can_delete=True)
model = models.Model
url_kwarg = 'url_kwarg'
form_class = formset
template_name = 'some-template.html'
extra_context = {'some_object': models.Model2.objects.all,
'model_object': models.Model.objects.all,
'formset': formset,
'view_type_create': True
}
def __init__(self, *args, **kwargs):
kwargs['url_kwarg']= self.kwargs.get(self.url_kwarg)
super().__init__(self,*args,**kwargs)
def get(self, request, *args, **kwargs):
request.session['url_kwarg'] = self.kwargs.get(self.url_kwarg)
return super().get(self, request, *args, **kwargs)
def post(self, request, *args, **kwargs):
#this works so I'm not re-typing it
def get_context_data(self, **kwargs):
"""Insert the form into the context dict."""
if 'url_kwarg' not in kwargs:
kwargs['url_kwarg'] = self.kwargs.get(self.url_kwarg)
return super().get_context_data(**kwargs)
#this works, but only in the get_context. Its not working as a solution to my problem.
def get_success_url(self):
#this works, not re-typing
My template has Javascript to handle multiple formsets, and I've tested it with a non-dynamic queryset. The only piece I'm having trouble with is taking the keyword argument from the URL and passing it to the form at init.
Have you tried using FormView.get_form_kwargs method?
Its described in docs
class AddOjbectsView(FormView):
def get_form_kwargs(self):
form_kwargs = super().get_form_kwargs()
form_kwargs['url_kwarg'] = self.kwargs['url_kwarg']
return form_kwargs
It would seem that the answer to my problem is to deprecate the process in favor of session variables at form validation, making use of the clean data functions. My methods above were generated from ignorance of the order of django operations, and shouldn't preoccupy anyone any further.

How get request user data in django rest to representation function

I need to access request.user in to_representation function, I tried self.context['request'] but self.context is empty dictionary. Is there anyway to access request.user or any way that I can push this data to this function?
def to_representation(self, obj):
print(self.context)
#output is an empty dictionary {}
UPDATE
class RetrieveView(generics.RetrieveAPIView):
def get(self, request, *args, **kwargs):
uid = kwargs.get('uid')
try:
item = self.model.nodes.get(uid=uid)
except Exception as e:
# error response
serializer = self.serializer_class(item)
return HttpSuccessResponse(SuccessResponse(serializer.data).to_json(), status=status.HTTP_200_OK).send()
class TopicRetrieveView(single_result.RetrieveView):
model = Topic
serializer_class = topic.TopicSerializer
ALL CODES are from django rest framwork Generic views generic.py
serializer_class is attribute we set in class definition or we need to override get_serializer_class function. It will handle in this function:
def get_serializer_class(self):
"""
Return the class to use for the serializer.
Defaults to using `self.serializer_class`.
You may want to override this if you need to provide different
serializations depending on the incoming request.
(Eg. admins get full serialization, others get basic serialization)
"""
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class
get_serializer_class will used in get_serializer function:
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
and context will fill by get_serializer_context function.
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
So correct usage is serializer = self.get_serializer(item) because it will use serializer_class for serializing item and fill context with extra information that may be helpful. serializer = self.serializer_class(item) can be used for just serializing item with no more extra information.

creating two models in one serializer of django rest framework

During registration of a user I would like to have both a User object and a EmailContact object created in one api call. The two objects should not be linked.
I have the following serializer:
class RegistrationSerializer(serializers.Serializer):
userserializer=UserAccountSerializer() #reuse existing modelserializer
emailcontactserializer=EmailContactSerializer() #reuse existing modelserializer
def create(self, validated_data):
emailcontact_data = validated_data.pop('emailcontactserializer')
user_data = validated_data.pop('userserializer')
emailcontact= EmailContact.objects.create(**emailcontact_data)
user= User.objects.create(**user_data)
return user
and the following Apiview:
class RegistrationAPIView(APIView):
permission_classes = (AllowAny,)
serializer_class = RegistrationSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The error I get is the following (occurs after the serializer.save()):
AttributeError at /api/register
Got AttributeError when attempting to get a value for field userserializer on serializer RegistrationSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the User instance.
Original exception text was: 'User' object has no attribute 'userserializer'.
In your RegistrationSerializer.create() method, you're returning a User object. The serializer will try to serialize that object into this representation:
{
'userserializer': x,
'emailcontactserializer': y
}
But it's complaining because the User you returned doesn't have a userserializer field.
If you really want to return a User from this API call, you could make your RegistrationSerializer a ModelSerializer with Meta.model=User, and override perform_create to pop out the emailcontact_data. (I'd name the field something like RegistrationSerializer.email_contact to make the representation clearer, IMO the phrase "serializer" shouldn't be present on the client-visible portion of the API).
Alternatively, if you want to render both of your sub-serializers, you can create a RegistrationSerializer instance in RegistrationSerializer.create by passing in the data, something like
return RegistrationSerializer(data={'emailcontactserializer':
emailcontact_data, 'userserializer': user_data})

Django Rest Framework - separate serializer class per method in model based API view

Say I have a simple Django REST Framework view that's extending multiple model classes and serves all the methods in one URL endpoint:
class UserAPIView(RetrieveAPIView, DestroyAPIView, BaseObjectAPIView):
permission_classes = (IsAuthenticated, )
serializer_class = UserSerializer
def get_serializer_class(self, *args, **kwargs):
# return different serializer depending on method??
# return UserUpdateSerializer
return UserViewSerializer
def get(self, request, *args, **kwargs):
"""
Retrieve user details
"""
# ...
return Response(data={'result': "OK"}, status=200)
def delete(self, request, pk):
"""
Delete user
"""
# ...
return Response(data={'result': "OK"}, status=200)
def put(self, request, pk):
"""
Change user
"""
# ...
return Response(data={'result': "OK"}, status=200)
Now I need to use different serializers per method, as my get-method will use different fields than my put-method, example serializers:
class UserViewSerializer(serializers.ModelSerializer):
firstname = serializers.Field(source='firstname')
lastname = serializers.Field(source='lastname')
username = serializers.Field(source='username')
class Meta:
model = User
class UserUpdateSerializer(serializers.ModelSerializer):
firstname = serializers.Field(source='firstname')
lastname = serializers.Field(source='lastname')
class Meta:
model = User
Is it possible to use different serializers for each method in my model based API view?
UPDATE:
I know how to use different serializers inside the methods themselves.
But I need to get the Browsable API generated by Swagger (Django module rest_framework_swagger) to retrieve different serializers for each method.
I can see that loading the API browser page triggers get_serializer_class, but inside that method, I don't know what method Swagger tries to get the serializer for.
How can I get rest_framework_swagger to retrieve different serializers per method?
I think there are at least two ways to achieve this:
You simply set the serializer that you want in each of your methods. Like this:
def get(self, request, *args, **kwargs):
self.serializer_class = UserViewSerializer
# ...
return Response(data={'result': "OK"}, status=200)
You override the get_Serializer_class method. Like this:
def get_serializer_class(self, *args, **kwargs):
if self.request.method == 'POST':
return UserUpdateSerializer
return UserViewSerializer
Hope this helps.
I suppose you could use yaml docstring on each method to override serializers. Like:
def put(self, request, pk):
"""Change user
---
serializer: .serializers.UserUpdateSerializer
"""
# ...
return Response(data={'result': "OK"}, status=200)

Resources