How can you customize the response message of the post in django? - python

I am trying to make a membership API using django rest frameworks.I made a code and checked that the function was working properly. In my current code, if the data of the email, password, and username are empty, the message is given as follows.
{
"email": [
"This field is required."
],
"username": [
"This field is required."
],
}
But after talking about this with my team's client developers, they said it was better to give a unified message as below.
{
"message": "email field is required."
}
How can I customize the value like this? Here's my code.
class customSignUpView (GenericAPIView) :
serializer_class = customRegisterSerializer
def post (self, request) :
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user = User.objects.get(email=serializer.data['email'])
token = RefreshToken.for_user(user).access_token
current_site = get_current_site(request).domain
relativeLink = reverse('emailVerify')
absurls = F'http://{current_site}{relativeLink}?token={token}'
email_body = F'Hi {user.username} Use link below to verify your email \n{absurls}'
data = {'email_body': email_body, 'to_email': user.email, 'email_subject': 'Verify your email'}
Util.send_email(data)
return Response({'message': 'check your email.'}, status=201)

you need to customize customRegisterSerializer further by adding a custome validate method to it, just try to do something like this
class YourSerializer(serializers.Serializer):
field1 = serializers.CharField(args)
...
fieldn = serializers.CharField(args)
def validate(self, data):
error = {}
if 'some_field' in data:
test field is valid here
if data['some_field'] is not valid:
error['some_field'] = 'your message'
.... ad nauseam
if error:
raise serializers.ValidationError(error)
return data
pass the arguments as the data parameter, and you should be able to customize everything however you want

First of all would like to say that the standard DRF approach to error messages is more universal, as it allows sending several messages for several fields in a unified way. I.e. that in the returned JSON key is the field name and value - the list of messages. Which also allows FE to display the messages next to the appropriate field.
Cause with the format you're trying to achieve comes the question of what kind of message to send if both email and username were not provided but are required (for e.g. should it be one message string or a list of "{field_name} is required" strings?).
But if you really need to achieve the approach you mentioned, let me elaborate on the answer by #vencaslac. So in your case the serializer should roughly look like:
class CustomRegisterSerializer(serializers.ModelSerializer):
...
def validate(self, data):
if not data.get("email"):
raise serializers.ValidationError({"message": "email field is required."})
return data
The validate() method is the same for both Serializer and ModelSerializer. You can find more info in the docs. But again, with this approach you need to figure out an answer to the question I mentioned above.

Related

Problem of signature with webauthn on django with djoser

I'm working at the moment on an implementation of webauthn on a project. The main point is to give the possibility to user to use FaceId or fingerprint scan on their mobile on the website.
I tried the djoser version of webauthn but I wanted to give the possibility to user that already have an account so I took the implementation of webauthn of djoser and I updated it to make it working with already created account.
I can ask for the signup request of a webauthn token and create the webauthn token with the front (Angular) where I use #simplewebauthn/browser ("#simplewebauthn/browser": "^6.3.0-alpha.1") . Everything is working fine there.
I use the latest version of djoser by pulling git and the version of webauthn is 0.4.7 linked to djoser.
djoser #git+https://github.com/sunscrapers/djoser.git#abdf622f95dfa2c6278c4bd6d50dfe69559d90c0
webauthn==0.4.7
But when I send back to the backend the result of the registration, I have an error:
Authentication rejected. Error: Invalid signature received..
Here's the SignUpView:
permission_classes = (AllowAny,)
def post(self, request, ukey):
co = get_object_or_404(CredentialOptions, ukey=ukey)
webauthn_registration_response = WebAuthnRegistrationResponse(
rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
origin=settings.DJOSER["WEBAUTHN"]["ORIGIN"],
registration_response=request.data,
challenge=co.challenge,
none_attestation_permitted=True,
)
try:
webauthn_credential = webauthn_registration_response.verify()
except RegistrationRejectedException as e:
return Response(
{api_settings.NON_FIELD_ERRORS_KEY: format(e)},
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.get(username=request.data["username"])
user_serializer = CustomUserSerializer(user)
co.challenge = ""
co.user = user
co.sign_count = webauthn_credential.sign_count
co.credential_id = webauthn_credential.credential_id.decode()
co.public_key = webauthn_credential.public_key.decode()
co.save()
return Response(user_serializer.data, status=status.HTTP_201_CREATED)
And I based my work on https://github.com/sunscrapers/djoser/blob/abdf622f95dfa2c6278c4bd6d50dfe69559d90c0/djoser/webauthn/views.py#L53
Here's also the SignUpRequesrtView where I edited some little things to make it working the way I want:
class SignupRequestView(APIView):
permission_classes = (AllowAny,)
def post(self, request):
CredentialOptions.objects.filter(username=request.data["username"]).delete()
serializer = WebauthnSignupSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
co = serializer.save()
credential_registration_dict = WebAuthnMakeCredentialOptions(
challenge=co.challenge,
rp_name=settings.DJOSER["WEBAUTHN"]["RP_NAME"],
rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
user_id=co.ukey,
username=co.username,
display_name=co.display_name,
icon_url="",
)
return Response(credential_registration_dict.registration_dict)
And I also updated the WebAuthnSignupSerializer to retrieve an check if there's an account with the username given and if yes, create the CredentialOptions:
class WebauthnSignupSerializer(serializers.ModelSerializer):
class Meta:
model = CredentialOptions
fields = ("username", "display_name")
def create(self, validated_data):
validated_data.update(
{
"challenge": create_challenge(
length=settings.DJOSER["WEBAUTHN"]["CHALLENGE_LENGTH"]
),
"ukey": create_ukey(length=settings.DJOSER["WEBAUTHN"]["UKEY_LENGTH"]),
}
)
return super().create(validated_data)
def validate_username(self, username):
if User.objects.filter(username=username).exists():
return username
else:
raise serializers.ValidationError(f"User {username} does not exist.")```
EDIT: TL;DR;
#simplewebauthn/browser encodes the signature as base64url while duo-labs/py_webauthn expects a hex encoded signature.
Well, it's not really an answer, but rather a little "assistance".
You can check whether the signature is valid using this little tool (at the bottom of the page): https://webauthn.passwordless.id/demos/playground.html
At least, using that, you will know if your data is correct or if something was stored wrongly. There are so many conversions from bytes to base64url and back that it's not always easy to track. Perhaps it is a data format/convertion issue? Like not converting to bytes, or accidentally double encoding as base64url.
Lastly, the stored public key has a different format depending on the algorithm. Either "raw" or "ASN.1" wrapped, in case you have a problem with the key itself.
Good luck!
EDIT:
While delving a bit into the source code of sunscrapers/djoser, I noticed something quite odd. While all data is encoded as base64, it appears the signature is hex encoded instead, see their test app
That seems to be because it uses duo-labs/py_webauthn as dependency which expects a hex encoded signature. On the other hand, the #simplewebauthn/browser lib encodes it into base64url, like all other data.
The verify() method expects a RegistrationResponse object as an argument, but you're passing it the entire request data. You need to extract the registrationResponse field from the request data and pass that to the verify() method instead.
Change this:
webauthn_registration_response = WebAuthnRegistrationResponse(
rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
origin=settings.DJOSER["WEBAUTHN"]["ORIGIN"],
registration_response=request.data, # <-- update this line
challenge=co.challenge,
none_attestation_permitted=True,)
To this:
webauthn_registration_response = WebAuthnRegistrationResponse(
rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
origin=settings.DJOSER["WEBAUTHN"]["ORIGIN"],
registration_response=request.data['registrationResponse'], # <-- update this line
challenge=co.challenge,
none_attestation_permitted=True,)

Pass django model query as json response

I'm working on a social network. I want to load the comments of each post so I make an API call to the server to fetch all the comments of the required posts. The code will make everything clear:
urls.py
path("comments/<int:post_id>", views.load_comments)
models.py
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
commented_by = models.ForeignKey(User, on_delete=models.CASCADE)
comment = models.CharField(max_length=128)
views.py
def load_comments(request, post_id):
"""Returns the comments to index.js"""
try:
# Filter comments returned based on post id
post = Post.objects.get(pk=post_id)
comments = list(Comment.objects.filter(post=post).values())
return JsonResponse({"comments": comments})
except:
return JsonResponse({"error": "Post not found", "status": 404})
index.js
fetch(`/comments/${post_id}`)
.then(res => res.json())
.then(res => {
// Appoints the number of comments in the Modal title
document.querySelector("#exampleModalLongTitle").textContent = `${res.comments.length} Comments`;
res.comments.forEach(comment => {
modal_body = document.querySelector(".modal-body")
b = document.createElement("b");
span = document.createElement("span");
br = document.createElement("br");
span.classList.add("gray")
b.textContent = comment.commented_by_id + " ";
span.textContent = comment.comment;
modal_body.appendChild(b);
modal_body.appendChild(span);
modal_body.appendChild(br);
})
I can get the comment value using comment.comment in js. However, the problem arises when I convert the comments objects to list.values so now I lose the ability to get the user who posted the comment (commented_by)
Any help to get the comment and commented_by values is appreciated.
I also tried in views.py:
def load_comments(request, post_id):
"""Returns the comments to index.js"""
try:
# Filter comments returned based on post id
post = Post.objects.get(pk=post_id)
post_comments = Comment.objects.filter(post=post)
comments = []
for comment in post_comments:
comments.append({
"comment": comment.comment,
"commented_by": commented_by,
})
comments = serializers.serialize('json', comments)
return HttpResponse(comments, content_type='application/json')
except:
return JsonResponse({"error": "Post not found", "status": 404})
However when I try this, it outputs the error.
In Django, the values() function for a ForeignKey field by default returns the id of the related object with the key formed by appending the name of the field with "_id". So in your case, you have your user ID under the key commented_by_id.
If by "loosing the ability to get the user who posted the comment" you mean other user info, like username, then you can pass the fields that you need to the values() function.
comments = list(Comment.objects
.filter(post=post)
.values("comment",
"commented_by__username"
))
will give a dict with the comment text and the user name (assuming that you have a username field in your User model.
1- you should use django_Rest_framework cuz it's very easy to work with api and show related field
2- use Post.objects.get(id=post_id) instead of pk
or you should skip this step by filter the comments depend on post id directly likeComment.objects.all().filter(post_id=post_id)

How to block/override djoser user/me endpoint?

I would like to know if there is a chance to block the djoser's user/me endpoint for specific request methods. I don't see this in the docs.
Problem: Now when I have two users { email: 'a#exa.com' }, and { email: 'b#exa.com' }. I can use the first user to change his email to match the 2nd user's email, so both will have the same email, and the second one will be blocked cos of that.
Is there an elegant way to check if the email exists from the djoser's level?
Djoser maintainer here.
I would like to know if there is a chance to block the djoser's user/me endpoint for specific request methods. I don't see this in the docs.
You'd need to subclass UserViewSet and change me action or add custom permissions in get_permissions.
So if you wanted to disable/limit PUT
class MyCustomUserViewSet(UserViewSet):
def get_permissions(self):
if self.action == "me" and self.request.method == "PUT":
# do something
return super().get_permissions()
or
class MyCustomUserViewSet(UserViewSet):
#action(["get", "patch", "delete"], detail=False)
def me(self, request, *args, **kwargs):
return super().me(request, *args, **kwargs)
Problem: Now when I have two users { email: 'a#exa.com' }, and { email: 'b#exa.com' }. I can use the first user to change his email to match the 2nd user's email, so both will have the same email, and the second one will be blocked cos of that.
You should always have unique or pk constraint on user email. It's not djoser's responsibility to guarantee unique email in your DB.
Is there an elegant way to check if the email exists from the djoser's level?
If you use unique then it won't be 2xx and you will know something went wrong. There's no way to "check" if email exists from the djoser level and there will never be as its purpose is to be a generic REST auth for Django.

Return custom JSON response from ListAPIView Django 3.0 Rest Framework

I have created an API using DRF That is able to list and view particular records based on the URL pattern specified. For example:
for the request:
curl -v http://127.0.0.1:8000/get_details/120001/
I am able to get a response:
[
{
"subject": "Data Structures",
"course": "CSE"
},
{
"subject": "Thermodynamics",
"course": "Chemistry"
},
{
"subject": "Organic Chemistry",
"course": "Chemistry"
},
{
"subject": "Optics",
"course": "Physics"
}
]
Where '120001' is the user_id the database is searched against.
But the I want the response in the following format:
{'Chemistry': ['Thermodynamics', 'Organic Chemistry'], 'CSE': ['Data Structures'], 'Physics': ['Optics']}
(content wise, I am not considering indentation and other factors)
While I am able to write code for the logic of how to create and populate this dictionary, I am unable to figure out how to return this as response and from where.
I am using generics.ListAPIView as the view class.
Here is my model (models.py):
class Subject(models.Model):
user_id = models.CharField(null = False, max_length=10)
subject = models.CharField(max_length=50)
course = models.CharField(max_length=50)
def __str__(self):
return self.subject
Serializer (serializers.py):
class SubjectSerializer(serializers.ModelSerializer):
class Meta:
model = Subject
fields = ['subject', 'course']
and, views.py (for the first output in default format):
class SubjectView(generics.ListAPIView):
serializer_class = SubjectSerializer
def get_queryset(self):
username = self.kwargs['user_id']
return Subject.objects.filter(user_id = username).only('subject','course')
I have written a logic to create the dictionary to send as response (as described in my desired output) by extracting values using Subject.objects.values(....) and then looping through the results to create my dictionary but I just don't get where (that is, which function) to write it in and return from.
Is there any function provided by the generics.ListAPIView class that can allow me to do this? And if not, then what other alternative approach can I try?
I am an absolute beginner at Django and any help will be appreciated. Also, it will be of great help if anyone can suggest me a practical guide/tutorial/playlist from where I can learn DRF through code examples to speed up my learning process.
Thank you!
You need to override to_representation method of Serializer
from docs
There are some cases where you need to provide extra context to the
serializer in addition to the object being serialized.
class SubjectSerializer(serializers.ModelSerializer):
class Meta:
model = Subject
fields = ['subject', 'course']
def to_representation(self, instance):
data = super(SubjectSerializer, self).to_representation(instance)
# manipulate data here
return data

Django Rest Framework: Overwriting validation error keys

When translating my site to another language a problem occurred.
I want to handle Validation Errors properly and allow my front-end friends to display them well.
Is there a way to overwrite keys in response message of DRF when Validation Error happend?
What do I mean by that - I want to change this:
{
"name": ["This field is required."]
}
into:
{
"username": ["This field is required."]
}
Is there a way to do that without writing each and every one of validators?
You can change the name field in the ModelSerializer to username.
example:
class CustomSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='name')
class Meta:
model = ...
fields = ('username', ...)
Now in validation errors it will have the key username instead.

Categories

Resources