drf user serializer return custom data - python

I've been trying to create a drf app and wanted to achieve a sign in view that does two things:
set's the cookies automatically
returns the url and the username of the user
the issue is specifically in the validate function inside the serializer code
views.py:
class CookieTokenObtainPairView(TokenObtainPairView):
def finalize_response(self, request, response, *args, **kwargs):
if response.data.get("refresh"):
# the cookie part works well
# the part that doesn't is in the serializer below
user = UserLoginSerializer(data=request.data)
user = user.validate(data=request.data) if user.is_valid()
response.data["user"] = user.data if user.is_valid() else user.errors
return super().finalize_response(request, response, *args, **kwargs)
serializers.py
class UserLoginSerializer(serializers.HyperlinkedModelSerializer):
password = serializers.CharField(style={"input type": "password"}, write_only=True)
#
class Meta:
model = User
fields = (
"id",
"url",
"username",
"password",
)
# read_only_fields = ("id")
def validate(self, data):
data["username"] = self["username"]
data["password"] = self["url"]
return super().validate(data)
so as you can see the validate option is trying to get the username and the url data to return it, but instead it's trying to create a new account. so maybe the validate option was not right. I researched on the drf docs but there seem to be an entirely other function called create. so I don't know how validate is not working. maybe I'm supposed to type in another function

In your validate function, you cannot access self['username'] – you can only access user data through self.instance; but, otherwise, you only can access the instance if you passed it to the serializer in a construct like:
user_serializer = UserLoginSerializer(data=request.data, instance=user_obj)
What do you need is after user login, so I recommend to you this post: Login and Register User Django Rest Framewrok; I am pretty sure you can get what you need there.

Related

DRF - Custom serializer - prefill data by request.user

I have a model:
class Income(...):
user = ForeignKey(User....) # not null
type = ...
amount = ...
And a special non-model serializer that should (besides other things) create Income objects for this user.
class WelcomeWizardSerializer(serializers.Serializer):
applicant = serializers.HiddenField(default=serializers.CurrentUserDefault())
applicant_incomes = IncomeSerializer(many=True, required=True)
Obviously, I need to tell IncomeSerializer to use applicant as user.
Otherwise WelcomeWizardSerializer returns errors:
{'applicant_incomes': [{'user': [ErrorDetail(string='Toto pole je povinné.', code='required')]}, {'user': [ErrorDetail(string='Toto pole je povinné.', code='required')]}]}
How can I do that?
#action(['POST'], detail=False, url_path='from-welcome-wizard')
def from_welcome_wizard(self, request, pk=None):
serializer = WelcomeWizardSerializer(context={'request':request}, data=request.data)
if serializer.is_valid(): # not valid
serializer.save()
I know I should get applicant (as I don't want to use CurrentUserDefault in IncomeSerializer) and add it to the data['incomes'] objects but I don't know where/when is the right place/time.
As you can see from the error message, you have an issue with WelcomeWizardSerializer.applicant_incomes, not WelcomeWizardSerializer.applicant. WelcomeWizardSerializer.applicant is all right.
Apparently, you have a user field inside applicant_incomes, and you don't want a client to pass a user into it as well. So you need to replace IncomeSerializer with some new serializer, for example, IncomeDefaultUserSerializer. You will need a similar HiddenField inside it, like:
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
After that, you have to use IncomeDefaultUserSerializer inside WelcomeWizardSerializer, and your issue has to be solved.

Creating new model instance through views.py including args through a url

I am trying to create a new model instance every time a url is accessed. so far, I have the function working in my views.py, but when the new model instance is created, the fields are empty (because I have not specified what I'd like in those fields in views.)
views.py
def session_invent(self):
session = Session() # I can add field data in here, but I want to get it via the URL
session.save()
messages.success(self, f'session invented!')
return redirect('blog-home')
urls.py
path('session/invent/', views.session_invent, name="session-invent"),
models.py
class Session(models.Model):
uid = models.CharField(max_length=50)
cid = models.CharField(max_length=50)
qid = models.CharField(max_length=50)
aid = models.CharField(max_length=50)
session_date = models.DateTimeField(auto_now_add=True)
def qid_plus_aid(self):
return '{}_{}'.format(self.qid, self.aid)
def __str__(self):
return self.uid
def get_absolute_url(self):
return reverse('session-detail', kwargs={'pk': self.pk})
Ok, so here is what i am trying to pull off:
right now if i enter mywebsite.com/session/invent/ a new Session model instance is created with empty fields. Is there a way I can fill in those fields with args in the URL? For example, something like...
mywebsite.com/session/invent/?uid=test_uid&cid=test_cid&qid=test_qid&aid=test_aid
Finished Answered code:
From the answer below here is how the updated views.py should look:
def session_invent(request):
session = Session.objects.create(
uid=request.GET['uid'],
cid=request.GET['cid'],
qid=request.GET['qid'],
aid=request.GET['aid']
)
messages.success(request, f'session invented from URL!')
return redirect('blog-home')
So, If I enter the following URL, a new record is created in my database with the values in each field set from the URL:
mywebsite.com/session/invent/?uid=test_uid&cid=test_cid&qid=test_qid&aid=test_aidz
Yes, the parameters are stored in the querystring, and you can use request.GET for a dictionary-like representation of the querystring, so:
def session_invent(request):
session = Session.objects.create(
uid=request.GET['uid'],
cid=request.GET['cid'],
qid=request.GET['qid'],
aid=request.GET['aid']
)
messages.success(request, f'session invented!')
return redirect('blog-home')
This will raise a HTTP 500 in case one of the keys is missing in the request.GET. You can use request.GET.get(…) [Django-doc] to access an element with an optional default value.
A GET request is however not supposed to have side effects. It is furthermore quite odd for a POST request to have a querystring.

How to validate the password django rest

I am trying to make a rest application to communicate with my android application but it is stopping me the validation of the password.
I use the User model by default of django and I would like to try to make the server validate the password
I found some other interesting answers but the truth is that django is not my strong point (my specialty is android) and they do not explain well how to implement them in my view
restapp/views.py
class postRegister(APIView):
def post(self,request):
data = JSONParser().parse(request)
cencripM=CriptoMovil(KEY_ENC_M)
data['username'] = cencripM.decrypt(data['username'])
data['email'] = cencripM.decrypt(data['email'])
data['password'] = cencripM.decrypt(data['password'])
serializer = RegistSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response({"message":"save","state":"1"})
return Response({"message":serializer.errors,"state":"2"})
maybe it helps some of the articles that I found but I did not understand how to implement them in the view (I repeat my specialty is android)
many options but I did not know how to implement
interesting but I did not understand how to implement the view
As beginning you don't need to write your customer serializer for validation instead you can follow token base authentication to validate in android as below:
urls.py
from rest_framework.authtoken.views import ObtainAuthToken
urlpatterns +=[
url(r'^api-token-auth/', ObtainAuthToken.as_view(), name='get_auth_token')
]
Now you can post username and password at /api-token-auth/ and if it is valid you will get a token in response and response status will be 200 OK
if you need to customise response then you need to override post method of
ObtainAuthToken as below:
class CustomAuthentication(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, context={'request': request}) # this will use DRF's AuthTokenSerializer and pass your request to it
serializer.is_valid(raise_exception=True) # validate serializer
user = serializer.validated_data['user'] # you will get user instance if it is valid
token, created = Token.objects.get_or_create(user=user) # gives you token for user
response_data = {'token': token.key} # create dict with token key
# you can add any other JSON serializable details you want to add in this dict like username or related role/email
return Response(response_data)
Now in urls.py instead of using ObtainAuthToken.as_view() you need to use
CustomAuthentication.as_view() and
For other setup detail read this thread

Differences between using Resource Model (Tastypie) and single Rest-Framework function

First of all, I am new to Python and Django, I am trying to understand the pros/cons of apis by single function and apis by resources (Tastypie's ModelResources).
I had been working on a Django project and been using rest-framework along all the way. However, I created each function for each api from the server. For example, this is a simple register function I wrote using rest-framework:
#api_view(['POST'])
#permission_classes((AllowAny,))
#parser_classes((JSONParser,))
def register(request):
if request.data is not None:
try:
username = request.data['username']
password = request.data['password']
user = User.objects.create_user
(username=utils.encrypt(username),
password=utils.encrypt(password))
......
This is another register example using Tastypie's ModelResource:
class RegisterUserResource(ModelResource):
class Meta:
allowed_methods = ['post']
always_return_data = True
authentication = Authentication()
authorization = Authorization()
queryset = User.objects.all()
resource_name = 'register'
.......
def obj_create(self, bundle, **kwargs):
try:
username = bundle.data["username"]
password = bundle.data["password"]
if User.objects.filter(username=username):
raise CustomBadRequest(
code=status.HTTP_201_CREATED,
message="This username is taken"
)
# Create object
bundle.obj = User.objects.create_user(username=username, password=password)
bundle.obj.save()
# Create user profile object
UserInfo.objects.create(user=bundle.obj)
except KeyError:
raise CustomBadRequest(
code="missing_key",
message="Must provide {missing_key} when creating a user."
)
except User.DoesNotExist:
pass
return bundle
Please help me to understand what are the differences between those two ways and which way is better. I personally think using Tastypie's ModelResource is pretty complicated and sometime hard to control object's permission (I could PATCH any object by id even that object was not created by me -- I tried to find those info in the Tastypie's documentations but haven't found something related)..

Django - How to make an optional field required based on request

I want to be able to save a form as a draft when it's not completely filled up and also save it as usual with classic django form validation.
To do so, I have two submit buttons in my form and I find out in my post request which button has been clicked :
class MyView(UpdateView):
def post(self, request, *args, **kwargs):
def submit_draft(self, request):
if 'draft' in request.POST:
out = True
else:
out = False
return out
In my models, all my field allow blank fields so saving an incomplete form as draft causes no issue if fields are empty.
I would like to make the form fields required when the form is saved with the normal save action.
One option I have thought of so far but didn't successfully implement :
=> Override get_form function so that when I hit Save as draft it just does it's normal action and when I hit Save, it modifies my fields required attribute.
tldr: I'm looking for a way to do something like that to my form based on the submit button that's been clicked
for field in form:
self.fields[field].required = True
Solved my problem by subclassing my forms as follows :
My form based on my model with null and blank = True
class RegionForm(forms.ModelForm):
class Meta:
model = Region
fields = ['region_name',
'region_description'
]
def __init__(self, *args, **kwargs):
super(RegionForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = True
class RegionDraftForm(RegionForm):
class Meta(RegionForm.Meta):
pass
def __init__(self, *args, **kwargs):
super(RegionDraftForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
That way I can instantiate in my views the form I need to save as draft or to normally save with complete form validation.
A solution I have used in the past is to serialise the content of the form into a "drafts" table when the "save draft" button is clicked.
When a user comes back to editing the form you load the json back into the form for continued editing. This can be done either using javascript or within the django form view.
Some steps you might follow:
When "Save Draft" is clicked, post the form to the draft view.
Serialise the content into json; storing the content in a "drafts model".
When a user click to re-open a draft pull the content from the database, parsing the json back into the form.
When the user clicks "Save" you save the form contents into the database and delete the draft from the "drafts table".
This is an example of what your Drafts model could look like.
class Drafts(models.Model):
user = models.ForeignKey(User)
form_name = models.CharField(max_length=100)
serialised_form = models.TextField() # could use json field here
def to_dict(self):
return json.loads(self.serialised_form)
I see this method having a number of advantages over

Categories