DRF - Custom serializer - prefill data by request.user - python

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.

Related

drf user serializer return custom data

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.

How to redirect from a CreateView of model A to another CreateView of a model B while passing key information from model A to the next view?

I am new to Django and do not know what I need to know to do this task. I am tasked with creating a web app that has two models. Model A is the employee and model B is a company that contains many employees. During signup, I have a form for model A. Once model A form is filled out, I need to pass an id from the employee to the company signup url so that when I save the company model to the table, I can make sure that the employee id is stored and so the two tables are related. How do I go about sending the employee_id to the company form page? Do I need to use some sort of redirect?
Flow: dashboard/employee_signup -> dashboard/company_signup -> completed_signup
I've looked through multiple tutorials on Django and most seem to be too simple to solve what I need done.
Here is my EmployeeSignUpView. Right now it redirects to a 'login' page. I need to instead redirect to a CompanySignUpView while passing along an employee_id. A company can't have zero employees, so the first person to signup for the company needs to be stored in the company model. The company table includes a column that stores a list of employees in that company. So a OneToMany relationship.
class EmployeeSignUpView(CreateView):
form_class = FSPEmployeeCreationForm
success_url = reverse_lazy('login')
template_name = 'employee_signup.html'
You redirect to something like
reverse('b:create_b', kwargs={"pk":a.pk}
The URL (in app 'b') is something like
url(r'create/(?P<pk>\d+)/$', b_create.as_view(), name='create_b'),
And you can pick up this parsed pk and convrt it into a full object by subclassing the dispatch method in b_create:
def dispatch( self, request, *a, **kw):
# resolve kwarg to self.a_object
self.a_object = get_object_or_404( A_model, pk = self.kwargs.get('pk','?') )
return super().dispatch( request, *a, **kw)
Doing this early means that the rest of your view code can everywhere reference self.a_object. It might be more efficient to do this only in post or form_valid, if it's not needed to (say) generate default values for the initial get of the form, or not needed until all the posted data is valid.
Instead of providing a static success_url, you can define get_success_url to return a URL that depends on the created object.
Assuming your CompanySignUpView has a URL as follows:
path('company_sign_up/<int:employee_id>/', views.CompanySignUpView.as_view(), name='company_sign_up')
then you would do:
class EmployeeSignUpView(CreateView):
form_class = FSPEmployeeCreationForm
template_name = 'employee_signup.html'
def get_success_url(self):
return reverse('company_sign_up', kwargs={'employee_id': self.object.id})
Edit
In your CompanySignUpView you can get the employee ID in the form_valid method:
class CompanySignUpView(CreateView):
...
def form_valid(self, form):
form.instance.initial_employee_id = self.kwargs['employee_id'] # or whatever the field name is
return super().form_valid(form)

Accessing a hyperlinkedRelatedField object from a permission class

I am trying to make an api backend of something like reddit. I want to ensure that whoever is creating a post (model Post) within a particular subreddit is a member of that subreddit (subreddit model is Sub). Here is my latest effort, which works but seems pretty sloppy, and the serializer for some context.
Post permissions.py
class IsMemberOfSubOrReadOnly(BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
elif request.data:
# prevent creation unless user is member of the sub
post_sub_pk = get_pk_from_link(request.data['sub'])
user = request.user
user_sub_pks = [sub.pk for sub in user.subs.all()]
if not (post_sub_pk in user_sub_pks):
return False
return True
Post serializers.py
from .models import Post
from redditors.models import User
from subs.models import Sub
class PostSerializer(serializers.HyperlinkedModelSerializer):
poster = serializers.HyperlinkedRelatedField(
view_name='user-detail',
#queryset=User.objects.all(),
read_only=True
)
sub = serializers.HyperlinkedRelatedField(
view_name='sub-detail',
queryset=Sub.objects.all()
)
class Meta:
model = Post
fields = ('url', 'id', 'created', 'updated', 'title', 'body',
'upvotes', 'sub', 'poster')
The issue with this approach is that since 'sub' is a hyperlinkedRelatedField on the Post serializer what I get back from request.data['sub'] is just the string hyperlink url. I then have a function, get_pk_from_link that uses regex to read the pk off the end of the url. Then I can use that to grab the actual model I want and check things. It would be nice if there were a more direct way to access the Sub model that is involved in the request.
I have tried searching the fields of the arguments that are available and I can't find a way to get to the Sub object directly. Is there a way to access the Sub model object through the hyperlink url?
I have also solved this problem by just using a serializer field validator (not shown above), but I am interested to know how to do it this way too. Maybe this is just a bad idea and if so please let me know why.
You are right, parsing the url is not the way to go. Since you want to perform the permission check before creating a Post object, I suspect you cannot use object level permissions either, because DRF does not call get_object in a CreateAPIView (since the object does not exist in the database yet).
Considering this is a "business logic" check, a simpler approach would be to not have that permission class at all and perform the check in your perform_create hook in your view (I had asked a similar question about this earlier):
from rest_framework.exceptions import PermissionDenied
# assuming you have a view class like this one for creating Post objects
class PostList(generics.CreateApiView):
# ... other view stuff
def perform_create(self, serializer):
sub = serializer.get('sub') # serializer is already validated so the sub object exists
if not self.request.user.subs.filter(pk=sub.pk).exists():
raise PermissionDenied(detail='Sorry, you are not a member of this sub.')
serializer.save()
This saves you hassle of having to perform that url parsing as the serializer should give you the Sub object directly.

non_field_errors : ["Expected a list of items but got type "dict"."]

I create realtime chat application using websocket frontend(angular) backend(Django).. I want to store messages in to db(mySql).. when I trying to store message from angular to django.. it give me error:
non_field_errors:
["Expected a list of items but got type "dict"."]
so what is wrong?
model.py
class msg(models.Model):
name = models.ForeignKey(User, on_delete=models.CASCADE)
receiver = models.CharField(max_length=20)
text = models.CharField(max_length=1200)
myDate = models.DateTimeField()
serializer.py
class MesSerializer(serializers.ModelSerializer):
name = serializers.SlugRelatedField(many=False, slug_field='name', queryset=User.objects.all())
class Meta:
model = msg
fields = '__all__'
view.py
class msg_list(APIView):
def get(self, request, format=None):
mes = msg.objects.all()
serializer = MesSerializer(mes, many=True) # convert into JSON
return Response(serializer.data)
def post(self, request, formate = None):
serializer = MesSerializer(data=request.data, many = True) #type list
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
The trouble is not with the slug. It is that you have used many = True in the view when you pass the data to the serializer, but you are in fact only sending a single message - which is why it is a dict and not a list. Remove that parameter.
def post(self, request, formate = None):
serializer = MesSerializer(data=request.data)
I recently came across a problem which is similar to the OP. Hence would like to share my experience and solution.
I have a dictionary of items, each of them unique. I wanted to use PUT to update an existing item. So I used objects.filter to fetch the object based on the name passed via JSON Request(I know I should have used pk, but I didn't because the items are unique and will remain so). Then I created a Django REST Serializer class object to save the updated object but I failed. This is because I wasn't using many = True. But when I did use it, I faced another error:
"non_field_errors": [
"Expected a list of items but got type \"dict\"."
]
So I finally removed both many=True as well objects.filter.
Instead I used objects.get. This solved the problem because objects.get returns the required object which I want to update whereas objects.filter returns a queryset object and not the actual object that I want to update. Of course objects.get will fail if I have multiple results, in which case I need to ensure there is a pk. Then again in my case objects.get will never return more than one object.
Hope this post helps someone and saves a lot of their time. :-)

KeyError from customized clean() method in BaseModelFormset

I have read over the Forms and Formset Django documentation about 100x. To make this very clear, this is probably the first time I've ever used super() or tried to overload/inherit from another class (big deal for me.)
What's happening? I am making a django-model-formset in a view and I am passing it to a template. The model that the formset is inheriting from happens to be a ManyToMany relationship. I want these relationships to be unique, so that if my user is creating a form and they accidentally choose the same Object for the ManyToMany, I want it to fail validation.
I believe I have written this custom "BaseModelFormSet" properly (via the documentation) but I am getting a KeyError. It's telling me that it cannot find cleaned_data['tech'] and I am getting the KeyError on the word 'tech' on the line where I commented below.
The Model:
class Tech_Onsite(models.Model):
tech = models.ForeignKey(User)
ticket = models.ForeignKey(Ticket)
in_time = models.DateTimeField(blank=False)
out_time = models.DateTimeField(blank=False)
def total_time(self):
return self.out_time - self.in_time
The customized BaseModelFormSet:
from django.forms.models import BaseModelFormSet
from django.core.exceptions import ValidationError
class BaseTechOnsiteFormset(BaseModelFormSet):
def clean(self):
""" Checks to make sure there are unique techs present """
super(BaseTechOnsiteFormset, self).clean()
if any(self.errors):
# Don't bother validating enless the rest of the form is valid
return
techs_present = []
for form in self.forms:
tech = form.cleaned_data['tech'] ## KeyError: 'tech' <-
if tech in techs_present:
raise ValidationError("You cannot input multiple times for the same technician. Please make sure you did not select the same technician twice.")
techs_present.append(tech)
The View: (Summary)
## I am instantiating my view with POST data:
tech_onsite_form = tech_onsite_formset(request.POST, request.FILES)
## I am receiving an error when the script reaches:
if tech_onsite_form.is_valid():
## blah blah blah..
Isn't the clean method missing a return statement ? If I remember correctly it should always return the cleaned_data. Also the super call returns the cleaned_data so you should assign it there.
def clean(self):
cleaned_data = super(BaseTechOnsiteFormset, self).clean()
# use cleaned_data from here to validate your form
return cleaned_data
See: the django docs for more information
I used the Django shell to call the forms manually. I found that I was executing the clean() method on all of the forms returned from the view. There were 2 filled out with data, and 2 blank. When my clean() method was iterating through them all, it returned a KeyError when it got to the first blank one.
I fixed my issue by using a try-statement and passing on KeyErrors.

Categories