Django Rest Framework: Different Validations for HTTP POST and PUT - python

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)

Related

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)

KeyError when passing kwargs to ModelForm

I'm trying to create a custom field for my M2M field in my ModelForm. ConnectedTo is the many to many field. Code below:
views:
def addPartForm_Create(request, site, subtype):
siteselected = site
sitenumber = str(site)
print(sitenumber)
subtypeselected = Subtype.objects.get(SubtypeID = subtype)
if request.method == 'POST':
form = addPartForm(request.POST, sitenum=sitenumber)
if form.is_valid():
obj = form.save(commit=False)
obj.SiteID = Site.objects.get(SiteID = siteselected)
obj.Subtype = subtypeselected
obj.save()
form.save_m2m()
return redirect('/sites/'+str(site))
else:
form = addPartForm()
return render(request, 'myproj/addPart.html', {'form': form, 'SiteNo': Site.objects.get(SiteID = siteselected).SiteID, 'subtype': subtypeselected})
forms:
class addPartForm(forms.ModelForm):
class Meta:
model = Part
fields = ('Comment', 'Location', 'ConnectedTo', 'BatchNo', 'Manufacturer', 'Length', 'InspectionPeriod')
labels = {"BatchNo": "Batch Number", "InspectionPeriod": "Inspection Period"}
def __init__(self, *args, **kwargs):
super(addPartForm, self).__init__(*args, **kwargs)
sitenum = kwargs.pop('sitenum')
self.fields["ConnectedTo"].widget = forms.CheckboxSelectMultiple()
self.fields["ConnectedTo"].queryset = Part.objects.filter(SiteID = sitenum)
I get KeyError when I try to pass sitenum from view to form. I know I could set a default value None but I don't want it to ever display none. I need to always have a sitenum sent here. Am I passing this wrong?
You need to pass the kwarg sitenum to all instances of your form, you aren't doing this when the request is GET. I'd also move all kwargs.pop() above the super calls to ensure it doesn't conflict with kwargs that the super might be expecting.

How can i use PUT method to edit Serialized data

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
)

Add extract fields to Django model serializer while doing validations

Supposed I had the following model, and modelSerializer:
models.py
class Approve(models.Model):
process = models.IntegerField(verbose_name='Associated Process')
content = models.CharField(max_length=300, verbose_name="Approval Content")
serializers.py
class ApproveSerializer(serializers.ModelSerializer):
class Meta:
model = Approve
fields = ('id', 'process' ,'content')
def validate(self, value):
process = value['process']
try:
something_else = Something.objects.get(process)
except Something.DoesNotExist:
raise serializers.ValidationError('Invalid Process')
return value
The thing is that I want to validate the incoming data in serializers.py, instead of views.py. You can notice that in order to make a validation check, I had query the database for something_else.
The problem is that I want to use this object something_else in the views.py, instead of making another database query. Is there any ways I can pass it with the serializer, without causing a serializer validation error when call serializer.is_valid() method.
Any suggestions will be welcomed, thanks in advance.
Well i am not sure how your views.py might look but essentially, you will need to instantiate the serializer there:
def approve_view(request):
if request.method == "POST":
process = request.POST.get('value', None)
something_else = Something.objects.filter(process=process).first()
serializer = ApproveSerializer(something_else=something_else) # This is where you pass the something_else object.
if serializer.is_valid():
# Your code for successful validation.
else:
# Your code for failed validation
Now that you have passed something_else into the ApproveSerializer, you need to set it up as a property:
class ApproveSerializer(serializers.ModelSerializer):
def __init__(self, something_else=None):
self.something_else = something_else
class Meta:
model = Approve
fields = ('id', 'process' ,'content')
def validate(self, value):
process = value['process']
if not self.something_else:
raise serializers.ValidationError('Invalid Process')
return value
I figure out a way to address the problem.
serializers.py
class ApproveSerializer(serializers.ModelSerializer):
class Meta:
model = Approve
fields = ('id', 'process' ,'content')
def validate(self, value):
process = value['process']
try:
something_else = Something.objects.get(process)
value['something_else'] = something_else
except Something.DoesNotExist:
raise serializers.ValidationError('Invalid Process')
return value
views.py
Before calling the serializer.save() method, you should pop the value that had been added to the serializer.
def post(self, request):
serializer = ApproveSerializer(data=request.data)
if serializer.is_valid():
something_else = serializer.validated_data.pop('something_else')
something_else.property = new_property
something_else.save()
serializer.save()
else:
# Error handling goes here serialzier.errors
Not sure this is a good practice, but at least it works for me right now. Hoping to know a better solution.

django REST ModelSerializer serialization issue

Good morning everybody!
I've a problem with django REST ModelSerializer.
This is my serializer:
class TransactionPayOutSerializer(serializers.ModelSerializer):
author = serializers.SlugRelatedField(queryset=Users.objects.all(), slug_field='unique_id', allow_null=True)
wallet = serializers.SlugRelatedField(queryset=Wallet.objects.all(), slug_field='wallet_id')
bank_account = serializers.SlugRelatedField(queryset=BankAccount.objects.all(), slug_field='bank_account_id')
class Meta:
model = PayOut
fields = ('author', 'wallet', 'amount',
'currency', 'bank_account')
def __init__(self, **kwargs):
if 'instance' in kwargs:
super(TransactionPayOutSerializer,self).__init__(kwargs.get('instance'))
else:
self.merchant = kwargs.pop('merchant')
super(TransactionPayOutSerializer, self).__init__(**kwargs)
def validate(self, data):
print 'Inside serializer validate(), data:'
print data
wallet = data['wallet']
user_wallet = UserWallets.objects.get(wallet=wallet)
if self.merchant != user_wallet.merchant:
raise serializers.ValidationError("Wallet not found")
wallet.fetch()
if wallet.balance < data['amount']:
raise serializers.ValidationError("Not enough founds in wallet")
return data
def create(self, validated_data):
print 'Inside TransactionPayOutSerializer create():'
print 'Validated data:'
print validated_data
pay_out = PayOut(**validated_data)
user_wallet = UserWallets.objects.get(wallet=validated_data['wallet'])
pay_out.author = user_wallet.user
pay_out.merchant = self.merchant
pay_out.save()
return pay_out
It works perfectly but my problem is:
When the serializer is used for a get request (serializer.data) it's perfect.
But when the serializer is used for a post request (TransactionPayOutSerializer(data=request.DATA)) it absolutely wants the 'user' parameter in the JSON.
What is the best way to keep serialzer.data return 'user' representation making TransactionPayOutSerializer(data=request.DATA) works without receiving 'user' as parameter inside JSON post request?
I should use simple serializers instead of ModelSerializers?
Thanks in advance!
Serializer Fields
read_only Set this to True to ensure that the field is used when
serializing a representation, but is not used when creating or
updating an instance during deserialization.
Defaults to False
write_only Set this to True to ensure that the field may be used when
updating or creating an instance, but is not included when serializing
the representation.
Defaults to False

Categories