I'm trying to set some fields before saving an object that a user wants to insert. For example, if a user wants to create a new instance, before saving it, I want to set the field owner equal to request.user and then call the create method from the parent. I've achieved this with the following code:
class ClassView(viewsets.ModelViewSet):
queryset = ModelClass.objects.all()
serializer_class = ModelClassSerializer
def create(self, request, pk = None):
if ModelClass.objects.filter(pk = request.user.id):
return Response({'detail' : "This user is already inserted" }, status = status.HTTP_401_UNAUTHORIZED)
return super(ClassView, self).create(request, pk = None)
def pre_save(self, obj):
obj.user_id = ModelClass.objects.get(pk = self.request.user.id)
It could be also that I want to set an attribute of the model according to some calculation with values coming from the POST request (those values are established as fields in the serializer).
Is the pre_save solution the correct way to go or am I missing something?
Thanks in advance.
I would say this is the correct way to go but if you simply want to set the object's user to the current request user, instead of:
obj.user_id = ModelClass.objects.get(pk = self.request.user.id)
...just use:
obj.user = self.request.user
The rest framework pre_save hook is there for your exact requirement but there exists other ones you may find useful. See http://www.django-rest-framework.org/api-guide/generic-views#genericapiview - under Save / deletion hooks.
However, if you require this data to be saved on the object instance outside of the rest framework (i.e. additionally within a normal Django view) you will most probably want to use the Django pre_save signal and hook your model up to it. That way the request user will be stored each time the object is saved, not just via the rest framework: https://docs.djangoproject.com/en/dev/ref/signals/
Related
I have a website that has users each user has his own profile, the problem is when the user wants for example edit his email or username and save this appear profile_image: The submitted data was not a file. Check the encoding type on the form. I thought that the frontend problem and I tried to edit the user username field without touch the image field in the Django rest framework API screen but it shows the same problem the user image field has a path to his image in getting but in put the image input is empty, how I can get edit the user other fields without loss the user image
my view
class UserProfileRetrieveUpdate(generics.GenericAPIView):
serializer_class = UserProfileChangeSerializer
def get(self, request, *args, **kwargs):
serializer = UserProfileChangeSerializer(
instance=request.user)
return Response(serializer.data)
def put(self, request, *args, **kwargs):
user_profile_serializer = UserProfileChangeSerializer(
instance=request.user,
data=request.data,
)
if user_profile_serializer.is_valid():
user_profile_serializer.save()
print(user_profile_serializer.data)
return Response(user_profile_serializer.data, status=status.HTTP_200_OK)
errors = dict()
errors.update(user_profile_serializer.errors)
return Response(errors, status=status.HTTP_400_BAD_REQUEST)
My serializer
class UserProfileChangeSerializer(serializers.ModelSerializer):
profile_image = serializers.ImageField()
class Meta:
model = Account
fields = ('pk', 'email', 'username', 'UserFullName', 'bio', 'profile_image')
There are multiple ways to approach this problem.
A. Add a PATCH request to the class UserProfileRetrieveUpdate in your view.
The slight difference between the PUT and PATCH request can solve the problem.
The difference between PUT and PATCH request.
With PUT request, the client is requesting a full update to the resource and hence must send all the values for the attributes of a model class. Thus, the new instruction replaces the previous one completely. Missing out on certain key value pairs in the request, would lead to setting the default values assigned in the model class.
With PATCH request, the client is requesting a partial update to the resource and hence shall send only the values for the attributes of a model class that need updating. Thus, the new instruction updates the previous instruction with the received key value pairs in the request.
Hence, if we use the PATCH request, then there would be no need to send the profile_image data and still it will remain preserved.
Suggested Code
A new method patch needs to be added to the class UserProfileRetrieveUpdate in your view, and the user_profile_serializer shall be assigned with attribute partial=True to the UserProfileChangeSerializer as it indicates that there will be partial updating of data instead of the default, which is full updating.
def patch(self, request, *args, **kwargs):
user_profile_serializer = UserProfileChangeSerializer(
instance=request.user,
data=request.data,
partial=True
)
if user_profile_serializer.is_valid():
user_profile_serializer.save()
return Response(user_profile_serializer.data, status=status.HTTP_200_OK)
return Response(user_profile_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
B. Modify models.py
This section entails additional changes that can help improve the code.
To accommodate these solutions, edit the class UserProfile in the models.py file.
1. Keep in mind
DON'T ever set ImageField with null=True as Django stores the path from MEDIA_ROOT to file/image in a CharField.
2. Setting imagefield as optional
If the ImageField can be optional, then set the attribute blank=True as it will allow empty values in the database.
profile_image = models.ImageField(upload_to='image_directory', blank=True)
Please Note: image_directory is the path directly in MEDIA_ROOT.
3. Setting a default image (only beneficial for POST request)
If there is a default image that can be set when no image path is passed to the ImageField, then set the attribute default=default.jpg. This is beneficial for POST requests with no value passed to the profile_image as it will set its path to default.jpg.
profile_image = models.ImageField(upload_to='image_directory', default=default.jpg)
Please Note: default.jpg path must exist in the MEDIA_ROOT.
I Solved this qustion by 1- remove the profile_image = serializers.ImageField() from the serializer 2- I added this function to the UserProfileChangeSerializer this can get the user profile image url
def get_profile_image_url(self, obj):
return obj.profile_image.url
Is there a way to use UpdateView without an object to update at first so I can pass it through AJAX? I want to create some sort of unique UpdateView for all the item where I can just select the item I want to update from a select.
Can I do this with UpdateView or I am going to need to code the view from scratch_
Yes. In your UpdateView you should override get method (or post method, depending on what exactly you want to do), you can choose what your view does after that, for example:
...
def get(self, request, **kwargs):
if 'id' in kwargs:
//perform update
else:
//do something else with AJAX
...
Yes you can do this by defining a get_object function in your view, and then accessing the relevant data in that function to determine which object is being updated.
https://docs.djangoproject.com/en/3.1/ref/class-based-views/mixins-single-object/#django.views.generic.detail.SingleObjectMixin.get_object
For example:
class SomeUpdateView(UpdateView):
...
def get_object(self):
#access info from the URL, like <pk> or <slug>
object_id = self.kwargs.get('pk')
#alternatively access the request.POST or request.GET data
some_info = self.request.POST.get('some_info')
#return a model instance based on some data in this function
return SomeModel.objects.get(pk=object_id)
...
I have a function in DRF where I want to tell the system to update "everybody", say I want to give everyone that logged in today a free prize and want to persist that list (I get this example is dumb, it just me trying to generalize my use case). It will query to see which users have signed in today and then I want to loop through those users to create a new activity of giving them a prize.
It appears to only be storing the last value in the loop on write.
time_threshold = datetime.now() - timedelta(hours=12)
signed_in_list = Activity.objects.filter(created__gte=time_threshold, activity="signed in")
for activity in signed_in_list:
serializer.save(user=activity.user_id, activity="gets free prize")
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
I'm trying to get it so that each user ID that is returned from the "signed_in_list" would have a new activity written.
I was expecting a new Activity entry to be written for each user_id in the loop, but it only is executing the last user_id in the loop. That makes me think the serializer.save is working, but overriding itself each time in the loop (versus performing the write to the DB).
You refer to "for user in" and "user.id" when in fact it's not a user, those objects you are referring to are activities.
Ideally I'd like to see your serializer, but given you have passed the same argument as the queryset command I assume the Serializer's model is Activity?
In which case you are attempting to save a new activity with the same primary key id as a previous existing activity which was returned in your queryset. This is an update, but to run an update on a serializer, the Activity instance would need to be passed to the serializer when it was initialized. In general, whether with Django models or DRF serializers, you never need to pass the id to save, especially not an existing id.
Assuming you have a foreign key on the activity model called "user" which references the User model you can do:
time_threshold = datetime.now() - timedelta(hours=12)
signed_in_list = Activity.objects.filter(created__gte=time_threshold, activity="signed in")
for activity in signed_in_list:
serializer.save(user=activity.user, activity="gets free prize")
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
Regarding "user=activity.user", passed as a keyword argument to serializer.save(), this is the way you would do it when saving a model. I think it's the same with DRF serializers, I'm not 100% sure but if it doesn't work you can try user=activity.user.id
I was able to resolve this. Rather than serializer.save, I updated the request and recursively called my create function
def create(self, request, *args, **kwargs):
time_threshold = datetime.now() - timedelta(hours=12)
signed_in_list = Activity.objects.filter(created__gte=time_threshold, activity="signed in")
for activity in signed_in_list:
request.data["user"] = activity.user
self.create(request, *args, **kwargs)
return Response(status=status.HTTP_201_CREATED)
I have created an api endpoint for one of my resources called "creative" . However I needed to have an action to implement pause and start of this resource (to change the field called status) . Hence I created a #detail_route() for the above actions .
What am I trying to achieve: I need to update a field of this particular resource(model) Creative as well as a field of another model whose foreign key is this model.
I am calling a custom function toggleAdGroupState inside this #detail_route() method where I directly work on the django models rather than using the serializers. Is this allowed ? Is this a proper way of implementing what i need to do ?
class CreativeSerializer(serializers.ModelSerializer):
class Meta:
model = Creative
fields = ('__all__')
#detail_route(methods=['post','get','put'])
def startcreative(self, request, pk=None):
try:
creative_object= self.get_object()
creative_object.active_status=Creative._STATUS_ACTIVE
creative_object.save()
toggleAdGroupState(creative_object.id,"active")
except Exception as e:
print 'error is ',str(e)
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(status=status.HTTP_200_OK)
def toggleAdGroupState(creative_id,toggleFlag):
adgroup_obj = AdGroup.objects.filter(creative=creative_id)
for item in adgroup_obj:
if(toggleFlag == 'active'):
item.userActivate()
else:
item.userInactivate()
djangorestframework is 3.2.2
I believe are following these steps
user clicks to do post/put request.
this request active_status from
model creative Next you want to update Another Model.
You can do like this in view for each method. I have shown for put,
class View(generics.UpdateAPIView):
....
def update(self, request, *args, **kwargs):
# update creative model
response = generics.UpdateAPIView.update(self, request, *args, **kwargs)
if response.status == 200:
# update_another_model()
When I try to deserialize some data into an object, if I include a field that is unique and give it a value that is already assigned to an object in the database, I get a key constraint error. This makes sense, as it is trying to create an object with a unique value that is already in use.
Is there a way to have a get_or_create type of functionality for a ModelSerializer? I want to be able to give the Serializer some data, and if an object exists that has the given unique field, then just return that object.
In my experience nmgeek's solution won't work in DRF 3+ as serializer.is_valid() correctly honors the model's unique_together constraint. You can work around this by removing the UniqueTogetherValidator and overriding your serializer's create method.
class MyModelSerializer(serializers.ModelSerializer):
def run_validators(self, value):
for validator in self.validators:
if isinstance(validator, validators.UniqueTogetherValidator):
self.validators.remove(validator)
super(MyModelSerializer, self).run_validators(value)
def create(self, validated_data):
instance, _ = models.MyModel.objects.get_or_create(**validated_data)
return instance
class Meta:
model = models.MyModel
The Serializer restore_object method was removed starting with the 3.0 version of REST Framework.
A straightforward way to add get_or_create functionality is as follows:
class MyObjectSerializer(serializers.ModelSerializer):
class Meta:
model = MyObject
fields = (
'unique_field',
'other_field',
)
def get_or_create(self):
defaults = self.validated_data.copy()
identifier = defaults.pop('unique_field')
return MyObject.objects.get_or_create(unique_field=identifier, defaults=defaults)
def post(self, request, format=None):
serializer = MyObjectSerializer(data=request.data)
if serializer.is_valid():
instance, created = serializer.get_or_create()
if not created:
serializer.update(instance, serializer.validated_data)
return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
However, it doesn't seem to me that the resulting code is any more compact or easy to understand than if you query if the instance exists then update or save depending upon the result of the query.
#Groady's answer works, but you have now lost your ability to validate the uniqueness when creating new objects (UniqueValidator has been removed from your list of validators regardless the cicumstance). The whole idea of using a serializer is that you have a comprehensive way to create a new object that validates the integrity of the data you want to use to create the object. Removing validation isn't what you want. You DO want this validation to be present when creating new objects, you'd just like to be able to throw data at your serializer and get the right behavior under the hood (get_or_create), validation and all included.
I'd recommend overwriting your is_valid() method on the serializer instead. With the code below you first check to see if the object exists in your database, if not you proceed with full validation as usual. If it does exist you simply attach this object to your serializer and then proceed with validation as usual as if you'd instantiated the serializer with the associated object and data. Then when you hit serializer.save() you'll simply get back your already created object and you can have the same code pattern at a high level: instantiate your serializer with data, call .is_valid(), then call .save() and get returned your model instance (a la get_or_create). No need to overwrite .create() or .update().
The caveat here is that you will get an unnecessary UPDATE transaction on your database when you hit .save(), but the cost of one extra database call to have a clean developer API with full validation still in place seems worthwhile. It also allows you the extensibility of using custom models.Manager and custom models.QuerySet to uniquely identify your model from a few fields only (whatever the primary identifying fields may be) and then using the rest of the data in initial_data on the Serializer as an update to the object in question, thereby allowing you to grab unique objects from a subset of the data fields and treat the remaining fields as updates to the object (in which case the UPDATE call would not be extra).
Note that calls to super() are in Python3 syntax. If using Python 2 you'd want to use the old style: super(MyModelSerializer, self).is_valid(**kwargs)
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
class MyModelSerializer(serializers.ModelSerializer):
def is_valid(self, raise_exception=False):
if hasattr(self, 'initial_data'):
# If we are instantiating with data={something}
try:
# Try to get the object in question
obj = Security.objects.get(**self.initial_data)
except (ObjectDoesNotExist, MultipleObjectsReturned):
# Except not finding the object or the data being ambiguous
# for defining it. Then validate the data as usual
return super().is_valid(raise_exception)
else:
# If the object is found add it to the serializer. Then
# validate the data as usual
self.instance = obj
return super().is_valid(raise_exception)
else:
# If the Serializer was instantiated with just an object, and no
# data={something} proceed as usual
return super().is_valid(raise_exception)
class Meta:
model = models.MyModel
There are a couple of scenarios where a serializer might need to be able to get or create Objects based on data received by a view - where it's not logical for the view to do the lookup / create functionality - I ran into this this week.
Yes it is possible to have get_or_create functionality in a Serializer. There is a hint about this in the documentation here: http://www.django-rest-framework.org/api-guide/serializers#specifying-which-fields-should-be-write-only where:
restore_object method has been written to instantiate new users.
The instance attribute is fixed as None to ensure that this method is not used to update Users.
I think you can go further with this to put full get_or_create into the restore_object - in this instance loading Users from their email address which was posted to a view:
class UserFromEmailSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = [
'email',
]
def restore_object(self, attrs, instance=None):
assert instance is None, 'Cannot update users with UserFromEmailSerializer'
(user_object, created) = get_user_model().objects.get_or_create(
email=attrs.get('email')
)
# You can extend here to work on `user_object` as required - update etc.
return user_object
Now you can use the serializer in a view's post method, for example:
def post(self, request, format=None):
# Serialize "new" member's email
serializer = UserFromEmailSerializer(data=request.DATA)
if not serializer.is_valid():
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
# Loaded or created user is now available in the serializer object:
person=serializer.object
# Save / update etc.
A better way of doing this is to use the PUT verb instead, then override the get_object() method in the ModelViewSet. I answered this here: https://stackoverflow.com/a/35024782/3025825.
A simple workaround is to use to_internal_value method:
class MyModelSerializer(serializers.ModelSerializer):
def to_internal_value(self, validated_data):
instance, _ = models.MyModel.objects.get_or_create(**validated_data)
return instance
class Meta:
model = models.MyModel
I know it's a hack, but in case if you need a quick solution
P.S. Of course, editing is not supported
class ExpoDeviceViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, ]
serializer_class = ExpoDeviceSerializer
def get_queryset(self):
user = self.request.user
return ExpoDevice.objects.filter(user=user)
def perform_create(self, serializer):
existing_token = self.request.user.expo_devices.filter(
token=serializer.validated_data['token']).first()
if existing_token:
return existing_token
return serializer.save(user=self.request.user)
In case anyone needs to create an object if it does not exist on GET request:
class MyModelViewSet(viewsets.ModelViewSet):
queryset = models.MyModel.objects.all()
serializer_class = serializers.MyModelSerializer
def retrieve(self, request, pk=None):
instance, _ = models.MyModel.objects.get_or_create(pk=pk)
serializer = self.serializer_class(instance)
return response.Response(serializer.data)
Another solution, as I found that UniqueValidator wasn't in the validators for the serializer, but rather in the field's validators.
def is_valid(self, raise_exception=False):
self.fields["my_field_to_fix"].validators = [
v
for v in self.fields["my_field_to_fix"].validators
if not isinstance(v, validators.UniqueValidator)
]
return super().is_valid(raise_exception)