django rest framework save serializer fail with post method - post

I'm trying to update a profile object with post method, but I get a error message when trying to save my serializer :
You cannot call `.save()` after accessing `serializer.data`.If you need to access data before committing to the database then inspect 'serializer.validated_data' instead.
My view :
class SettingsProfileView(APIView):
"""
Get and update user profile
"""
queryset = models.UserProfile.objects.all()
serializer_class = serializers.UserProfileSerializer
renderer_classes = [TemplateHTMLRenderer]
template_name = 'base_/settings/profile.html'
def get_object(self, pk):
try:
return models.UserProfile.objects.get(pk=pk)
except models.UserProfile.DoesNotExist:
raise Http404
def get(self, request, format=None):
if not request.user.is_authenticated:
return Response({"error": _("User is not connected")}, status=status.HTTP_511_NETWORK_AUTHENTICATION_REQUIRED)
try:
profile = request.user.profile
except models.UserProfile.DoesNotExist:
profile = models.UserProfile(user=request.user)
profile.key_name = request.user.username
profile.save()
profile = self.get_object(request.user.profile.id)
serializer = serializers.UserProfileSerializer(profile)
return Response({'serializer': serializer, 'profile': profile})
def post(self, request, format=None):
serializer = serializers.UserProfileSerializer(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 occured in this part : serializer.save() in my post method. Is it because the serializer is accessing data in his instentiation method ?
My serializer is very basic, it has no special code :
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('user', 'coachs', 'is_coach', 'gender', 'weight', 'height', 'visibility', )
Maybe the problem comes from the fact that I'm using post methode instead of update ?
EDIT (after #pleasedontbelong post) :
I've tried with generic view :
class SettingsProfileView(generics.GenericAPIView):
but the update method is not fired (because I come from a HTML post), so I had to manually raise update method like that :
def post(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object(request.user.profile.id)
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
But the error is still the same.
I cannot find any example where an object is updated with django rest by post method. Is it because it's not a good way to proceed ?

After hours of debugging, it appear that the problem comes from Visual Studio's breakpoints. After removing breakpoints it works fine.
Maybe Visual Studio try to read in serializer.data and then affects them.

It's better to use generic views, it prevents you from rewriting all this code. But if you rather do it manually, you can always check the source code to check how it's done:
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py#L61-L78
class UpdateModelMixin(object):
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
you must pass the UserProfile instance to the serializer
BTW: you should/must use PUT or PATCH for updating, POST is for creating objects.
Hope this helps :)

Related

Convert function params to Hash object in Ruby on Rails

I have to make a change to an API developed in Ruby On Rails that looks like this:
class V0::PythonsController < ApplicationController
skip_before_action :authorize_request
# POST v0/python/import
def import
result = { status: :error }
data = eval(AwsTool.decrypt(params["code"])).first
if data.class == Hash
user = User.find_by(id: data[:ot_user_id])
activity_type = ActivityType.find_by(id: data[:activity_type])
if user.nil?
result[:msg] = "user not found"
elsif activity_type.nil?
result[:msg] = "activity type not found"
else...
I pass to it some data in the "code" param, that is then decrypted and then explored. I want to add an if clause so when I call the API from a different origin no encryption and decryption takes place. So I have made this change:
class V0::PythonsController < ApplicationController
skip_before_action :authorize_request
# POST v0/python/import
def import
result = { status: :error }
if params["origin"] != 'trusted'
data = eval(AwsTool.decrypt(params["code"])).first
else
data = params["code"]
end
if data.class == Hash
user = User.find_by(id: data[:ot_user_id])
activity_type = ActivityType.find_by(id: data[:activity_type])
...
The problem is that data.class is not a Hash object, its a String. I have tried different solutions to convert the object from String to Hash like t_hash and other similar functions but they didn't work. I got some errors about params not being permitted, I tried to add the permit to them but still fails.
Any other idea?
It is failing because you forgot to call eval on the code. Do this:
data = eval(params["code"])
By the way, evaling input is very dangerous. I hope you trust whoever is using this API.

Populate a field on changeform_view

When I open an item in a model, for example: /shop_push/shopapidefinition/4/change/ I want to populate one field with my own value and leave the rest intact.
I can currently do something when I edit, like below
class ShopApiDefinitionAdmin(admin.ModelAdmin):
def __init__(self, *args, **kwargs):
# let's define this so there's no chance of AttributeErrors
self._request = None
super(ShopApiDefinitionAdmin, self).__init__(*args, **kwargs)
def get_request(self):
return self._request
def changeform_view(self, request, *args, **kwargs):
# stash the request
self._request = request
print "changelist_view ShopApiDefinitionAdmin"
# call the parent view method with all the original args
# ShopApiDefinitionAdmin['about_the_shop'] = "hello"
return super(ShopApiDefinitionAdmin, self).changeform_view(request, *args, **kwargs)
What I don't know how to do it add data to a field for that record, for example
field['about_the_shop'] = "new text"
It would be nice to save the new data, but that isn't essential.
Any guidance would be appreciated
Thanks
I realised that it was probably best to use change_view and that this triggers before the content is retrieved from the model, so what I do is used the object_id, query the record, do my code and then save the details
def change_view(self, request, object_id, form_url='', extra_context=None):
# Get the record
obj = ShopApiDefinition.objects.get(id=object_id)
# get a value as I use that in my code
self.password = obj.password
# do some code
shopify_shop = "a value from my code"
obj.about_the_shop = shopify_shop # change field
obj.save() # this will update only
return super(ShopApiDefinitionAdmin, self).change_view(
request, object_id, form_url, extra_context=extra_context,
)
That seems to work. It saved the value in the model, and when the form loads it is populated with both the existing data and then new data from my code
Thanks
Grant

Rails Handling Nil in Class Method Chaining

I have the following Search class method that takes a bunch of params and then builds a request.
def self.search(agent, params)
RealPropertySale.where(id: available_ids)
.joins(:address)
.by_state(state)
.by_suburb(suburb)
.by_post_code(post_code)
.by_country(country)
.paginate(page: page)
end
def self.by_state(state)
where(addresses: {state: state})
end
def self.by_suburb(suburb)
where(addresses: {suburb: suburb})
end
def self.by_post_code(post_code)
where(addresses: {post_code: post_code})
end
def self.by_country(country)
where(addresses: {country: country})
end
What is the correct way of handling if one of my custom class methods e.g. self.by_country(country) returns nil so that the query continues with whatever param/s are present. I have tried returning self if one the params is blank but the query is lost and the class is returned resulting in errors.
I agree with #Michael Gaskill that you probably should only call the scopes that actually affect the final query (i.e. that have meaningful params).
However, if you insist on the scopes ignoring nil parameters, you may make them return current_scope instead (which is an undocumented but useful method):
def self.by_state(state)
return current_scope if state.nil?
where(addresses: {state: state})
end
We did something similar by breaking it down like so :
response = RealPropertySale.where(id: available_ids)
.joins(:address)
response = response.by_state(state) if state
response = response.by_suburb(suburb) if suburb
response = response.by_post_code(post_code) if post_code
response = response.by_country(country) if country
response = response.paginate(page: page) if page
I like the readibility. I try to break it down to as many pieces as needed, but this should be adapted to your business logic. Don't know if, for you, it makes sense to check if suburb is provided for example.

Spree error when using decorator with the original code

Need a little help over here :-)
I'm trying to extend the Order class using a decorator, but I get an error back, even when I use the exactly same code from source. For example:
order_decorator.rb (the method is exactly like the source, I'm just using a decorator)
Spree::Order.class_eval do
def update_from_params(params, permitted_params, request_env = {})
success = false
#updating_params = params
run_callbacks :updating_from_params do
attributes = #updating_params[:order] ? #updating_params[:order].permit(permitted_params).delete_if { |k,v| v.nil? } : {}
# Set existing card after setting permitted parameters because
# rails would slice parameters containg ruby objects, apparently
existing_card_id = #updating_params[:order] ? #updating_params[:order][:existing_card] : nil
if existing_card_id.present?
credit_card = CreditCard.find existing_card_id
if credit_card.user_id != self.user_id || credit_card.user_id.blank?
raise Core::GatewayError.new Spree.t(:invalid_credit_card)
end
credit_card.verification_value = params[:cvc_confirm] if params[:cvc_confirm].present?
attributes[:payments_attributes].first[:source] = credit_card
attributes[:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
attributes[:payments_attributes].first.delete :source_attributes
end
if attributes[:payments_attributes]
attributes[:payments_attributes].first[:request_env] = request_env
end
success = self.update_attributes(attributes)
set_shipments_cost if self.shipments.any?
end
#updating_params = nil
success
end
end
When I run this code, spree never finds #updating_params[:order][:existing_card], even when I select an existing card. Because of that, I can never complete the transaction using a pre-existent card and bogus gateway(gives me empty blanks errors instead).
I tried to bind the method in order_decorator.rb using pry and noticed that the [:existing_card] is actuality at #updating_params' level and not at #updating_params[:order]'s level.
When I delete the decorator, the original code just works fine.
Could somebody explain to me what is wrong with my code?
Thanks,
The method you want to redefine is not really the method of the Order class. It is the method that are mixed by Checkout module within the Order class.
You can see it here: https://github.com/spree/spree/blob/master/core/app/models/spree/order/checkout.rb
Try to do what you want this way:
Create file app/models/spree/order/checkout.rb with code
Spree::Order::Checkout.class_eval do
def self.included(klass)
super
klass.class_eval do
def update_from_params(params, permitted_params, request_env = {})
...
...
...
end
end
end
end

How to render the output of my action

My controller:
def getMenuItemCount(String name){
def myCount = MenuItem.countByMenuItemCategory(name)
render myCount
}
What do i call in another gsp so that myCount appears. createLink doesnt seem to work
${createLink(action:'getMenuItemCount', params: [name:ci.name])}
A model has to be returned instead of rendering it in order to access it in a gsp.
def getMenuItemCount(String name){
[ myCount: MenuItem.countByMenuItemCategory(name) ]
}
Also, (not related to question really), try to avoid using action names as get* and set*. I have seen discrepancies with that nomenclature.

Resources