clean django api in serilizer - django-rest-framework

i write a django api i would like to know if reminder field changed then the Appointment model object save current user.
i used this link
See object changes in post_save in django rest framework
and write this code
class AppointmentBackOfficeViewSet(mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.CreateModelMixin,
viewsets.GenericViewSet):
model = Appointment
read_serializer_class = AppointmentSerializer
write_serializer_class = AppointmentCreateSerializer
reminder_change = False
def perform_update(self, serializer):
if 'reminder' in serializer.validated_data:
self.reminder_change = True
serializer.save()
def update(self, request, *args, **kwargs):
super(AppointmentBackOfficeViewSet, self).update(request, *args, **kwargs)
instance = self.get_object()
instance.user = request.user
if self.reminder_change:
instance.reminder_user = request.user
instance.save()
res = self.write_serializer_class(instance).data
return Response(res)
class AppointmentCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Appointment
exclude = ['created_time', 'modified_time']
is there a way to write this code clearer than this :
dont use self.reminder_change class field is there better way?
may be move this lines to serializer??(in serializer dont access to request.user)

Removed unecessary fields and update method:
class AppointmentBackOfficeViewSet(mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.CreateModelMixin,
viewsets.GenericViewSet):
model = Appointment
read_serializer_class = AppointmentSerializer
write_serializer_class = AppointmentCreateSerializer
def perform_update(self, serializer):
# Here you can set attributes directly to the serializer update method
# It's like setting attribute directly to the updating model
# Check that reminder has been changed
reminder = serializer.validated_data.get('reminder')
if reminder and reminder != instance.reminder: # Reminder is different
serializer.save(user=self.request.user,
reminder_user=self.request.user)
else:
serializer.save(user=self.request.user)
This solution will work depending on what type of reminder field is.
If it's String or Integer it will be ok. Problem is that if it's object. Viewset will raise error because serializer reminder field would be integer but instance.reminder would be instance of the reminder object so keep this in mind.

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!

How to update another field when using partial_update?

I'm using partial_updates on my user model, and I wish to change the is_active to True on the user model instance when a partial_update happens - even though is_active is not exposed to the endpoint. My class looks like this:
class UserInvitationUpdate(mixins.UpdateModelMixin, generics.GenericAPIView):
serializer_class = serializers.UserSerializer
queryset = User.objects.all()
def get(request, *args, **kwargs):
username = kwargs.get('username')
token = kwargs.get('token')
return activated_user(username, token)
def get_object(self):
username = self.kwargs.get('username')
user = User.objects.get(username=username)
return user
def put(self, request, *args, **kwargs):
username = self.kwargs.get('username')
token = self.kwargs.get('token')
if my_if_statement_is_true:
# TODO set user to active
# how do I set is_active = True for the user model instance?
return self.partial_update(request, *args, **kwargs)
You have multiple way to deal with that. You could either change your serializer .save() method and set manually the field is_active to true, or set it in the view by updating the perform_update() method of your view :
def perform_update(self, serializer):
serializer.save(is_active=True)
More info here

AttributeError: 'collections.OrderedDict' object has no attribute 'model_id' and 'model_id' is missing from visible fields

Something strange happened: I was defining an endpoint and initially two fields were visible in the API form: model_id and payload, as given in the model definition:
### models.py:
class CarModel(models.Model):
model_id = models.CharField(max_length=10, primary_key=True)
name = models.CharField(max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.model_id
class Calculator(models.Model):
model = models.ForeignKey(CarModel, on_delete=models.CASCADE)
payload = models.TextField()
def model_id(self):
return self.model.model_id
def __str__(self):
return f"Calculations for {self.model.name}"
### serializers.py:
class CalculatorSerializer(serializers.ModelSerializer):
model_id = serializers.SerializerMethodField()
class Meta:
model = Calculator
fields = ['model_id', 'payload']
def get_model_id(self, obj):
return obj.model_id()
### views.py:
class CalculatorViewSet(viewsets.ModelViewSet):
serializer_class = CalculatorSerializer
queryset = Calculator.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(f"{serializer.data.upper()}", status=status.HTTP_200_OK)
So, both fields were visible, but POST requests ended in the AttributeError: 'collections.OrderedDict' object has no attribute 'model_id'. Trying to fix that, I eventually and accidentally removed model_id from view - it doesn't display in DRF's forms. And the AttributeError still persists.
What is wrong with this piece of code?
OK, it turns out that defining fields in this manner:
fields = '__all__'
makes also the model_id visible. Still, no idea why explicit insert doesn't work.
In case of the other issue, the AttributeError, I had to pull the value out of an OrderedDict. Modified method looks like this:
def get_model_id(self, obj):
return obj["model"].model_id
Beside that, I found one more error inside views.py's create method: serializer.data won't implement upper() method; some key, in my case serializer.data['payload'], has to be referenced, so for example:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
res = {
"payload": f"{serializer.data['payload'].upper()}"
}
return Response(res, status=status.HTTP_200_OK)

Django: customizing the field types needed for create and retrieve serializers

I currently have the following serializer:
serializers.py
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = SurfInstructorSerializer(many=False)
surfers = SurferSerializer(many=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
def create(self, validated_data):
return SurfGroup(**validated_data)
And the following viewset create method (viewset inherited from viewsets.ViewSet as we need some bespoke customization, extra signals and actions etc):
viewsets.py
# Surf Group Create View:
def create(self, request, format=None):
serializer = SurfGroupSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response = responses.standardized_json_response(
message='Surf Group Objects Have Been Successfully Created',
data=serializer.data
)
return Response(data=response, status=status.HTTP_201_CREATED, headers=headers)
For the retrieve action, the serializer works well, and we have a nested instructor object in the response. However, I want to perform a create by passing in the instructor uuid attrbiute like (see content in the POST textarea):
Rather than a whole object...I was wondering how we achieve this? Is it best to just have two Serializers, one for performing the create, and one the retrieval?
def create(self, validated_data):
surf_group = SurfGroup(
instructor__uuid=validated_data['instructor'],
)
surf_group.save()
return surf_group
It is good question.
I work with this situations many times and it looks like one option is to have two serializers as you mean: 1 for list/retrieve and 1 for save.
Another option (for me) is to set serializer field input as UUID and output as another serializer data like this:
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = serializers.UUIDField()
surfers = SurferSerializer(many=True, read_only=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
# I use this validate method to transform uuid to object which will
# be bypassed to create method for easly save
def validate_instructor(self, instructor_uuid):
try:
return Instructor.objects.get(uuid=instructor_uuid)
except Instructor.DoesNotExist:
# Remember that you dont need to pass field_key: [errors] to ValidationError
# because validate_FIELD will automatically pass field_key for you !
raise ValidationError(['Instructor with the given uuid does not exist.'])
# Overwrite output data
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['instructor'] = SurfInstructorSerializer(instance=instance.instructor).data
return ret

Django 1.3 CreateView, ModelForm and filtering fields by request.user

I am trying to filter a field on a ModelForm. I am subclassing the generic CreateView for my view. I found many references to my problem on the web, but the solutions do not seem to work (for me at least) with Django 1.3's class-based views.
Here are my models:
#models.py
class Subscriber(models.Model):
user = models.ForeignKey(User)
subscriber_list = models.ManyToManyField('SubscriberList')
....
class SubscriberList(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=70)
....
Here is my view:
#views.py
class SubscriberCreateView(AuthCreateView):
model = Subscriber
template_name = "forms/app.html"
form_class = SubscriberForm
success_url = "/app/subscribers/"
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
return super(SubscriberCreateView, self).form_valid(form)
Here is my original form for adding a Subscriber, with no filter:
#forms.py
class SubscriberForm(ModelForm):
class Meta:
model = Subscriber
exclude = ('user', 'facebook_id', 'twitter_id')
Here is my modified form, attempting to filter, but doesn't work:
#forms.py
class SubscriberForm(ModelForm):
class Meta:
model = Subscriber
exclude = ('user', 'facebook_id', 'twitter_id')
def __init__(self, user, **kwargs):
super(SubscriberForm, self).__init__(**kwargs)
self.fields['subscriber_list'].queryset = SubscriberList.objects.filter(user=user)
If I change this modified form as so:
def __init__(self, user=None, **kwargs)
It works - It brings me NO subscriber lists. But any way I try to pass the request user, I invariably get a a name "request" or name "self" not defined error.
So, how can I modify my code to filter subscriber_list by the request.user, and still use Django 1.3's CreateView.
I see you've been posting this question in various places.. and the way I found that is because I was trying to figure out the same thing. I think I just got it working, and here's what I did. I overwrote get_form() from FormMixin to filter a specific form fields queryset:
class MyCreateView(CreateView):
def get_form(self, form_class):
form = super(MyCreateView,self).get_form(form_class) #instantiate using parent
form.fields['my_list'].queryset = MyObject.objects.filter(user=self.request.user)
return form

Resources