How can i use PUT method to edit Serialized data - python

I created an edit function to edit username of user.
def edit(request):
if request.user.is_authenticated:
data = request.POST
s = UserSerializer(data=data)
u = s.is_valid()
if u:
s.update(request.user.username,request.POST['username'])
return JsonResponse(
{
'message' : 'profile edited!'
},status=201
)
else:
return JsonResponse(
{
'message' : 'you are not login!'
},status=401
)
I don't know where PUT should be used and also how can I use update() .
and this is my serializer class :
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'username','email','password'
)
def validate_password(self,password):
password = make_password(password)
return password
def update(self,isinstance, validated_data):
instance.username = validated_data.get('username', instance.username)
instance.email = validated_data.get('email', instance.email)
return instance

in Django rest framework APIView class, you should use from put function instead of edit:
def put(request):
...
to use serializer, in your view function, use serializers.save() function:
def put(request):
....
s.save()
the save function, based on the view function will call update (for put function), create (for post function), and destroy (for delete function).
when you want to instantiate from the serializer class, you must pass the model object as instance and data as data attribute. in summary your view function will be something like this:
class YourView(APIView)
def put(request):
if request.user.is_authenticated:
s = UserSerializer(instance=request.user, data=request.POST)
if s.is_valid():
s.save()
return Response(
{
'message': 'profile edited!'
}, status=201
)
else:
return Response(
{
'message' : 'you are not login!'
},status=401
)

Related

Cant handle DoesNotExist error with nested resources

I'm having trouble handling DoesNotExist error, Im using DRF and DRF-Nested-Routers and when I create a new Like object I need the Photo PK so I can add it to the Like object.
I'm trying to catch the error that I get when the Photo with that PK doesn't exist.
This is how I'm doing the creation of the Like in the serializer:
class LikeModelSerializer(serializers.ModelSerializer):
""" Like model serializer. """
user = serializers.CharField(default=serializers.CurrentUserDefault())
class Meta:
""" Meta class. """
model = Like
fields = ('user', 'photo')
read_only_fields = ('user', 'photo')
def create(self, validated_data):
# Get the photo pk from the view context (DRF-nested-routers) and
# create the new like with the validated_data
photo_pk = self.context['view'].kwargs["photo_pk"]
try:
photo = Photo.objects.get(id=photo_pk)
except Photo.DoesNotExist:
return Response(data={'detail': "The photo doesn't exist."}, status=status.HTTP_404_NOT_FOUND)
validated_data["photo"] = photo
like, created = Like.objects.get_or_create(**validated_data)
if created:
photo.total_likes += 1
photo.save()
return like
The perform_create of the view:
def perform_create(self, serializer):
""" Creates a new like.
The substraction in the total_likes is made in the serializer.
"""
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(data=serializer.data, status=status.HTTP_200_OK)
The response I get with this is: {'user': 'my username here'}
I also tried with except Photo.DoesNotExist but it gives the same result.
It migth be more clear to perform the check in the validate method of the serializer class. In case of photo absence raise the serializers.ValidationError.
I have not test the code but I think that it works.
class LikeModelSerializer(serializers.ModelSerializer):
...
def validate(self, attrs):
photo_pk = self.context['view'].kwargs["photo_pk"]
try:
photo = Photo.objects.get(id=photo_pk)
except Photo.DoesNotExist:
raise serializers.ValidationError({"detail": "The photo doesn't exist"})
attrs["photo"] = photo
return attrs
def create(self, validated_data):
# Get the photo pk from the view context (DRF-nested-routers) and
# create the new like with the validated_data
like, created = Like.objects.get_or_create(**validated_data)
if created:
photo.total_likes += 1
photo.save()
return like
def perform_create(self, serializer):
""" Creates a new like.
The substraction in the total_likes is made in the serializer.
"""
if not serializer.is_valid():
raise ValidationError(serializer.errors)
serializer.save()
return Response(data=serializer.data, status=status.HTTP_200_OK)

ValueError when trying to create a following relationship in Django

I have looked through the similar SOverflow questions and no answer helped.
I am trying to create a follower/following relationship when a button is pressed, passing the info through Javascript...
function follow(user) {
fetch(`/profile/${user.id}`, {
method: 'POST',
body: JSON.stringify({
followed: user.id
})
})
.then(response => response.json())
.then(() => load_profile(user.id))
}
...then I receive it and try to save it or update it...
#csrf_exempt
#login_required
def view_profile(request, user_id):
if request.method == "POST":
data = json.loads(request.body)
followed = data.get("followed", "")
follower = request.user.id
obj, created = UserFollowing.objects.get_or_create(user_id=followed, following_user_id=follower, following=True)
if not created:
obj(following=False)
obj.save()
return JsonResponse({"message": "(Un)Followed successfully."}, status=201)
... and this is the model:
class User(AbstractUser):
pass
def serialize(self):
return {
"id": self.id,
"user": self.username,
"following": self.following.count(),
"followers": self.followers.count(),
}
class UserFollowing(models.Model):
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="following")
following_user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="followers")
following = models.BooleanField(blank=False, default=True)
ERROR: ValueError: Cannot assign "3": "UserFollowing.user_id" must be a "User" instance.
I have tried to pass different values such as the username, the object itself, to no avail.
This line:
followed = data.get("followed", "")
If your body message (data) does not contain followed keyword than empty string will be assigned to followed variable otherwise a number.
Both cases would make you trouble later at this line, where followed has to be a user object and can't be an empty string or a number.
obj, created = UserFollowing.objects.get_or_create(user_id=followed, following_user_id=follower, following=True)
You will also get the same error at the following line where you assign a number to the follower variable which, according to your model, has to be a user object as well.
follower = request.user.id
Possible solution to that:
#csrf_exempt
#login_required
def view_profile(request, user_id):
if request.method == "POST":
data = json.loads(request.body)
if "followed" in data:
followed = data["followed"]
try:
user = User.objects.get(id=followed)
except:
# user does not exist
follower = request.user
obj, created = UserFollowing.objects.get_or_create(user_id=user, following_user_id=follower, following=True)
if not created:
obj(following=False)
obj.save()
return JsonResponse({"message": "(Un)Followed successfully."}, status=201)

Not passing **kwargs to django-import-export resources from custom view/form

When I use this resources.py inside Django admin everything works fine. However, when I do it on my custom view page there is an issue that popped with the **kwargs user auto-populate.
The error must be in my view as it's not passing the **kwargs but I'm not sure how to solve it. Where should I be passing this information?
KeyError at /import/
'user'
C:\Users\winkl\tj3\venv\lib\site-packages\import_export\resources.py in import_row
self.after_import_instance(instance, new, **kwargs) …
C:\Users\winkl\tj3\portfolios\resources.py in after_import_instance
instance.created_by = kwargs['user']
resources.py
class EntryResource(resources.ModelResource):
symbol = fields.Field(
attribute="symbol",
column_name="symbol",
widget=SymbolWidget(Symbol, 'name'),
)
date = fields.Field(
attribute="date",
column_name="date",
widget=widgets.DateTimeWidget(format="%Y-%m-%d %H:%M:%S"),
)
class Meta:
model = Entry
fields = ('symbol', 'date', 'id', 'amount', 'price', 'fee', 'entry_type', 'reg_fee',)
import_order = fields
skip_unchanged = False
report_skipped = True
def after_import_instance(self, instance, new, row_number=None, **kwargs):
print(f' Kwargs: {kwargs}')
instance.created_by = kwargs['user']
def after_save_instance(self, instance, using_transactions, dry_run):
pass
view.py
#login_required
def import_data(request):
if request.method == 'POST':
trade_resource = EntryResource()
dataset = Dataset()
new_trades = request.FILES['importData']
imported_data = dataset.load(new_trades.read().decode('utf-8'),format='csv')
result = trade_resource.import_data(dataset, dry_run=True, raise_errors=True)
if result.has_errors():
messages.error(request, 'Uh oh! Something went wrong...')
else:
# Import now
trade_resource.import_data(dataset, dry_run=False)
messages.success(request, 'Your words were successfully imported')
return render(request, 'dashboard/import.html')
Obviously you are getting the error because there is no entry with key 'user' in the kwargs dict.
If you look at the source, you can see that the kwargs get passed down the call stack into after_import_instance().
In your case, at no point is there ever a user entry in the kwargs dict.
However, if you pass this value in to the import_data() call then it can be retrieved and used:
your_user = load_user() # implement this as required
result = trade_resource.import_data(dataset, dry_run=True, raise_errors=True, user=your_user)

Add non-model field in Serializer DRF

I'm trying to combine rasters model field with non-model config_version field into my serializer which looks like:
class RasterPostSerializer(serializers.ModelSerializer):
class Meta:
model = Raster
fields = (
'name',
'description',
'filepath'
)
class ConfigurationSerializer(serializers.Serializer):
config_version = serializers.CharField()
rasters = RasterPostSerializer(many=True)
def create(self, validated_data):
data_field = validated_data['rasters']
for raster in data_field['rasters']:
return Raster(name=raster['name'], description=raster['description'], filepath=raster['filepath'])
Before utilize serializer.save() method I would like to check config_version in my view.py, but after that the .save() gives me:
The serializer field might be named incorrectly and not match any attribute or key on the `Raster` instance.
Original exception text was: 'Raster' object has no attribute 'config_version'.
What is going on, and is there a solution for that?
EDIT:
my example JSON POST payload looks like:
{
"config_version":"v2",
"rasters":[
{
"name":"XYZ.jpg",
"description":"some description",
"filepath":"path in filesystem"
}
}
The config_version it is not constant. It can be set dynamically, due the controller utilizing ConfigurationSerializer will be the configuration endpoint - there need to be possibility to add rasters, and some other information in the future.
configuration endpoint in views.py looks like
#api_view(['POST'])
def configuration(request):
if request.method == 'POST':
serializer = ConfigurationSerializer(data=request.data)
if serializer.is_valid():
if serializer.validated_data['config_version'] is 'v2':
raster_name_list = [raster.name for raster in Raster.objects.all()]
for instance in serializer.validated_data['rasters']:
if instance['name'] in raster_name_list:
return Response({"Fail": "Raster name '{}' already exists".format(instance['name'])}, status=status.HTTP_304_NOT_MODIFIED)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
You should return ConfigurationSerializer data as the result of create action. And also i have no idea what you are going to do with config_version
class ConfigurationSerializer(serializers.Serializer):
config_version = serializers.CharField()
rasters = RasterPostSerializer(many=True)
def create(self, validated_data):
config_version = validated_data.get('rasters')
rasters = validated_data.pop('rasters')
raster_list = []
for raster in rasters:
raster_list.append(Raster.objects.create(**raster))
return {
"config_version": config_version,
"rasters": raster_list
}
You need a SerializerMethodField: http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
config_version = SerializerMethodField()
def get_config_version(self, obj):
return YOUR_CUSTOM_DATA

Django Rest Framework: Different Validations for HTTP POST and PUT

I have a custom validation function in my DataSerliazer which checks two parameters (mrange , mthreshold). The validation function checks that only one of the parameters needs to be set while posting.
class DataSerializer(serializers.ModelSerializer):
emails = serializers.ListField(child = serializers.EmailField())
class Meta:
model = AIData
fields = ('id', 'name', 'created', 'username', 'token',
'expression','key','threshold' ,'evaluator', 'range','emails','metric_name', 'status')
def validate(self,attrs):
mrange = attrs.get("metric_range")
mthreshold = attrs.get("metric_threshold")
if (mrange == None or mrange == " ") and (mthreshold == None or mthreshold == " "):
raise serializers.ValidationError({'error': 'Cannot have both range and threshold empty'})
elif mrange != None and mthreshold != None:
raise serializers.ValidationError({'error': 'Cannot set both range and threshold'})
In my views.py file
#api_view(['GET','PUT', 'DELETE'])
def ai_detail(request, token, id):
ai = None
try:
ai = AIData.objects.get(id=id)
except AIData.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = DataSerializer(ai)
if serializer.data['token'] == token:
return Response(serializer.data)
else:
return Response({'error': 'Not allowed to access'}, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'PUT':
serializer = AlAnotherSerializerMethod(alert, data=request.data, partial=True)
if serializer.is_valid():
// updating code
Currently, I have to write anthor AIAnotherSerializerMethod so that, it does not implicitly call the validate function in DataSerializer. This is to avoid checking the 2 parameters during object creation. Once the object is created , I dont want to check the same function anymore. Is there a neater way of doing the same ? I would preferably avoid the need for writing 2 Serializers for the same model.
You can pass context data to the serializer which could include the request itself.
You can access the context anytime anywhere in the serializer by calling self.context.
This solution does makes it easier to provide contextual data without overriding the __init__.
The rest-framework library doesn't have anything like that as far as I know.
The way I do it is to overide the __init__ and add your own variable to check this.
class DataSerializer(serializers.ModelSerializer):
emails = serializers.ListField(child = serializers.EmailField())
class Meta:
model = AIData
fields = ('id', 'name', 'created', 'username', 'token',
'expression','key','threshold' ,'evaluator', 'range','emails','metric_name', 'status')
def __init__(self, *args, **kwargs):
super(DataSerializer, self).__init__(*args, **kwargs)
self._request_method = kwargs.get('request_method', 'GET')
def validate(self,attrs):
if self._request_method == 'GET':
# GET logic
elif self._request_method in ('PUT', 'POST'):
# Put or Post logic
And then i'd use it like this:
serializer = DataSerializer(ai, request_method=request.method)

Categories