Django API REST : Issue with function in CreateAPIView - python

I'm working on my Django API Rest but it's pretty new for me and I have one more question about my code.
I'm reading all the time Django Rest Doc
------------------------------------------------------------------------------------------------------------------------------------
FIRST PART - My code :
------------------------------------------------------------------------------------------------------------------------------------
I have a serializer.py file which describe my ModelSerializer like this :
class IndividuCreateSerializer(serializers.ModelSerializer) :
class Meta :
model = Individu
fields = [
'Etat',
'Civilite',
'Nom',
'Prenom',
'Sexe',
'Statut',
'DateNaissance',
'VilleNaissance',
'PaysNaissance',
'Nationalite1',
'Nationalite2',
'Profession',
'Adresse',
'Ville',
'Zip',
'Pays',
'Mail',
'Telephone',
'Image',
'CarteIdentite',
]
def create(self, validated_data):
print('toto')
obj = Individu.objects.create(**validated_data)
Identity_Individu_Resume(self.context.get('request'), obj.id)
return obj
This create function calls another function which is not in my API module, but in my main module : Identity_Individu_Resume
As you can see here, this function takes the last created object and applies multiple processes :
#login_required
def Identity_Individu_Resume(request, id) :
personne = get_object_or_404(Individu, pk=id)
NIU = lib.Individu_Recherche.NIUGeneratorIndividu(personne)
personne.NumeroIdentification = NIU
if personne.Image != " " :
NewImageName = 'pictures/' + personne.Nom +'_'+ personne.Prenom +'_'+ NIU + '.jpg'
FilePath = settings.MEDIA_ROOT
FilePathOLD = settings.MEDIA_ROOT + str(personne.Image)
FilePathNEW = settings.MEDIA_ROOT + NewImageName
file = os.path.exists(FilePath)
if file :
os.rename(FilePathOLD, FilePathNEW)
personne.Image = NewImageName
if personne.CarteIdentite != " " :
NewCarteName = 'Carte_Identite/' + 'Carte_Identite_' + personne.Nom +'_'+ personne.Prenom +'_'+ NIU + '.jpg'
FilePath = settings.MEDIA_ROOT
FilePathOLD = settings.MEDIA_ROOT + str(personne.CarteIdentite)
FilePathNEW = settings.MEDIA_ROOT + NewCarteName
file = os.path.exists(FilePath)
if file :
os.rename(FilePathOLD, FilePathNEW)
personne.CarteIdentite = NewCarteName
else :
pass
personne.save()
context = {
"personne" : personne,
"NIU" : NIU,
}
return render(request, 'Identity_Individu_Resume.html', context)
Then, I have in my API module a views.py file with a specific class named IndividuCreateAPIView which is very simple and call my serializer class describes above :
class IndividuCreateAPIView(CreateAPIView) :
queryset = Individu.objects.all()
serializer_class = IndividuCreateSerializer
Then I have my urls.py file :
urlpatterns = [
url(r'^$', IndividuListAPIView.as_view() , name="IndividuList"),
url(r'^docs/', schema_view),
url(r'^create/$', IndividuCreateAPIView.as_view() , name="Create"),
]
------------------------------------------------------------------------------------------------------------------------------------
SECOND PART - Using API REST Interface :
------------------------------------------------------------------------------------------------------------------------------------
In this part, I'm using my API Rest interface with http://localhost:8000/Api/Identification/
When I create an object, the create function in my serializers.py file works well and I get a well-created object in my database :
So there is none issue !
------------------------------------------------------------------------------------------------------------------------------------
THIRD PART - Using API REST with pythonic file :
------------------------------------------------------------------------------------------------------------------------------------
In this case, I'm getting an issue.
I have a file API_create.py which is executed from my terminal and should simulate an external application which try to connect to my API and create an object.
import requests
url = 'http://localhost:8000/Api/Identification/create/'
filename1 = '/Users/valentin/Desktop/Django/DatasystemsCORE/Media/pictures/photo.jpg'
filename2 = '/Users/valentin/Desktop/Django/DatasystemsCORE/Media/Carte_Identite/carte_ID.gif'
files = {'Image' : open(filename1,'rb'), 'CarteIdentite': open(filename2,'rb')}
data = {
"Etat": "Vivant",
"Civilite": "Monsieur",
"Nom": "creation",
"Prenom": "via-api",
"Sexe": "Masculin",
"Statut": "Célibataire",
"DateNaissance": "1991-11-23",
"VilleNaissance": "STRASBOURG",
"PaysNaissance": "FR",
"Nationalite1": "FRANCAISE",
"Nationalite2": "",
"Profession": "JJJ",
"Adresse": "12, rue des fleurs",
"Ville": "STRASBOURG",
"Zip": 67000,
"Pays": "FR",
"Mail": "",
"Telephone": ""
}
response = requests.post(url, files=files, data=data)
print(response.text)
But, when I execute this script, my object is well-created BUT the function create in my serializers.py is not called !
In this case, I'm getting that :
My question is : Why my create function doesn't work ? I don't understand because my url is correct and should call this function in order to replace my NIU (NULL) by the generated NIU and change pictures names too ...
Could you help me to find why it doesn't work ?

Probably this is because of login_required decorator in Identity_Individu_Resume view. You can try to remove it or you need to provide auth token with request: response = requests.post(url, files=files, data=data, headers={'Authorization': 'Token <MY_TOKEN>'}).
UPD
Actually I suppose it would be better to move common part of api and non-api views to third function and call it separately from both views. In this case you probaly would like to add login_required to IndividuCreateAPIView view also. In this case you need to add IsAuthenticated permission like this:
from rest_framework.permissions import IsAuthenticated
class IndividuCreateAPIView(CreateAPIView) :
queryset = Individu.objects.all()
serializer_class = IndividuCreateSerializer
permission_classes = (IsAuthenticated,)

Related

How can I solve the Not found problem when getting from pytest-django through pk?

I have a problem with django-pytest
I'm using, djnago-rest-framework
There is a problem testing the details. As shown in the code below, I entered the same details, detail1, detail2, and detail3 codes. However, only detail1 succeeds and detail2, detail3 indicates that '/api/v1/stats/1/' could not be found. It also occurs when implementing delete. I am curious about the cause and solution of this error.
enter image description here
// tests/test_apis.py
import json
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from stats.models import Stats
class StatsApiTests(APITestCase):
def setUp(self):
Stats.objects.get_or_create(blockshots=1, memo='test1')
Stats.objects.get_or_create(blockshots=2, memo='test2')
self.create_read_url = reverse('api:stats:stats-list')
self.read_update_delete_url = reverse('api:stats:stats-detail', kwargs={'pk': '1'})
def test_detail1(self):
response = self.client.get(self.read_update_delete_url)
data = json.loads(response.content)
content = {
'blockshots': 1,
'memo': 'test1',
}
self.assertEqual(data, content)
def test_detail2(self):
response = self.client.get(self.read_update_delete_url)
data = json.loads(response.content)
content = {
'blockshots': 1,
'memo': 'test1',
}
self.assertEqual(data, content)
def test_detail3(self):
response = self.client.get(self.read_update_delete_url)
data = json.loads(response.content)
content = {
'blockshots': 1,
'memo': 'test1',
}
self.assertEqual(data, content)
def test_list(self):
response = self.client.get(self.create_read_url)
self.assertContains(response, 'test1')
self.assertContains(response, 'test2')
Its hard to know what your actual implementation for read_update_delete_url, hence I assume it is looking up the resource by primary key. In that case, you can simply add the primary key in the url like this:
stat_one, _ = Stats.objects.get_or_create(blockshots=1, memo='test1')
stat_two, _ = Stats.objects.get_or_create(blockshots=2, memo='test2')
self.read_update_delete_url = reverse('api:stats:stats-detail', kwargs={'pk': stat_one.pk})
Basically, get_or_create returns the object and the state of the object (created or not). You can use the object's id as the parameter of reverse function.

Proper way to create django rest endpoint without model

I have to create API endpoint that will take data from GET request body. It will find necessary data in already created database to perform calculation and then it should return calculation result as response.
So I found a way to do it but I'm not sure if it should look like this. It works but you know :)
urls.py
schema_view = get_schema_view(
openapi.Info(
title="API Test",
default_version='0.0.1',
description="Test",
),
public=True,
)
urlpatterns = [
path('docs/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
path('equation/1/example', EquationView.as_view()),
#... more paths to other equations...
]
view.py
def get_emission_val(req_emiss_fac):
try:
return Emission.objects.get(emission_factor=req_emiss_fac)
except Emission.DoesNotExist:
raise Http404("Emission value does not exist in database")
equation_response = openapi.Response('response description', EquationResponseSerializer)
#swagger_auto_schema(
query_serializer=EquationRequestSerializer,
responses={
'404':'Emission factor does not exist in database',
'200': equation_response
},
)
class EquationView(APIView):
def get(self, request):
emission_val = get_emission_val(request.data['emission_factor'])
combusted_val = request.data['combusted_value']
emissions = combusted_val * emission_val.co2_emission
return Response({'emissions':emissions})
serializers.py
class EquationRequestSerializer(serializers.Serializer):
emission_factor = serializers.CharField()
combusted_value = serializers.FloatField()
class EquationResponseSerializer(serializers.Serializer):
emission = serializers.FloatField()
What do you think about this approach? I'm not sure if I have to do it in this way. I'm using swagger and additional serializers for gerenerating api docs
Example get request body:
{
"emission_factor":"Test",
"combusted_value":1.0
}
Example response:
{
"emissions": 1.5129
}

How to use postman to upload DBRef data in json?

I'm trying to post data to mongodb using postman but I don't know the proper convention for uploading the reference to a image file in the fs.files bucket. Basically, the file is already in the database, I'm just trying to post a new user with the reference to the image.
Here is my model:
class Users(db.Document):
_id = db.StringField()
name = db.StringField()
picture = db.FileField()
email = db.StringField()
password = db.StringField()
meta = {'collection': 'Users'}
In postman, I try to post data like so:
{
"_id" : "1",
"name" : "John Doe",
"picture": [{"$id": "5e6a...f9q102"}], #This is the reference id for the image already in the database, in fs.files
"password" : "<hashed pw>",
"email" : "example#example.com"
}
I'm using flask restful api so in the python script, the post function is defined like so:
def post(self):
body = request.get_json()
print (body)
user = Users()
user = Users(**body).save()
return 'Successful Upload', 200
I get the error when I try with the above convention:
mongoengine.errors.ValidationError: ValidationError (Users:None) ('list' object has no attribute
'grid_id': ['picture'])
How do I post a new user in postman? Your help is appreciated
You need to change a bit your code
Add these imports:
from mongoengine.fields import ImageGridFsProxy
from mongoengine import ReferenceField, DynamicDocument
from bson.dbref import DBRef
from bson import ObjectId
Modify your class picture field definition + add extra class fs
class Fs(DynamicDocument):
#Add 'db_alias':'default' to meta
meta = {'collection': 'fs.files'}
class Users(Document):
...
picture = ReferenceField('Fs', dbref=True)
...
Now, you need to create new instance for DBRef this way:
def post(self):
body = request.get_json()
body["picture"] = DBRef('fs.files', ObjectId(body["picture"]))
#mongoengine assumes `ObjectId('xxx')` already exists in `fs.files`.
#If you want to check, run below code:
#if Fs.objects(_id=body["picture"].id).first() is None:
# return 'Picture ' + str(body["picture"].id) + ' not found', 400
user = Users(**body).save()
return 'Successful Upload', 200
At the end, if you need to read picture content:
image = ImageGridFsProxy(grid_id=ObjectId('xxx'))
f = open("image.png", "wb")
f.write(image.read())
f.close()
It was a Validation error. The database was accepting JSON in a particular format than what I was posting. And the way I was processing the post request was also incorrect. This is the format it expected:
{
...,
"picture" = {"$ref": "fs.files",
"$id": ObjectId("5e6a...f9q102")},
...
}
Postman cannot accept the above format, instead, it accepted this:
{
"_id" : "1",
"name" : "John Doe",
"picture": {"$ref": "fs.files", "$id": {"$oid": "5e6a...f9q102"}},
"password" : "<hashed pw>",
"email" : "example#example.com"
}
To make this work I changed the model to look this so in my flask app:
class Users(db.Document):
_id = db.StringField()
name = db.StringField()
picture = db.ReferenceField('fs.files') #I changed this to a reference field because it holds the reference for the file and not the actual file in the database
upload_picture = db.FileField() #I added this field so I can still upload pics via flask and via this document
email = db.StringField()
password = db.StringField()
meta = {'collection': 'Users'}
Then I had to add this import and change the code so that it would read the input as JSON and transfer the reference value of picture to ObjectId(id) so that it matches the format the database was expecting.
from bson.json_util import loads
def post(self):
body = str(request.get_json())
x = body.replace("'", '"') #replace single quotes as double quotes to match JSON format
data = loads(x)
officer = Officers(**data).save()
return 'Successful Upload', 200
Then voila, it works!

Django Rest Framework Angulars upload file using ModelSerializer

I'm trying to upload an image using Django Rest Framework and AngularJs
I'm using ng-file-upload to upload the image with AngularJs.
I saw it's possible to use ApiViewSet to do this. But as my Image is in a model and I use ModelSerializer so if it's possible I prefer to use ModelViewSet.
The problem is when I upload the image I got an error:
image: ["The submitted data was not a file. Check the encoding type on the form."]
Here is my code
Models.py
class Image(models.Model):
image = models.ImageField(upload_to="articles")
article = models.ForeignKey(Article)
Serializers.py
class ImageSerializer(serializers.ModelSerializer):
class Meta:
#article = ArticleSerializer(read_only=True, required=False)
image = serializers.ImageField(use_url=True, allow_empty_file=True)
model = Image
fields = ('id', 'image', 'article')
read_only_fields = ('id', )
#def get_validation_exclusions(self, *args, **kwargs):
# exclusions = super(ImageSerializer, self).get_validation_exclusion()
# return exclusions + ['article']
Views.py
class ImageViewSet(viewsets.ModelViewSet):
queryset = Image.objects.order_by('id')
serializer_class = ImageSerializer
class ImageArticleViewset(viewsets.ModelViewSet):
queryset = Image.objects.select_related('article').all()
serializer_class = ImageSerializer
def list(self, request, *args, image_pk=None):
queryset = self.queryset.filter(article__id=image_pk)
serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data)
And the Controller
(function () {
'use strict';
angular
.module(nameProject + '.article.post.controller')
.controller('ArticlePostController', ArticlePostController);
ArticlePostController.$inject = ["$scope", "Articles", "Upload", "$timeout"];
function ArticlePostController($scope, Articles, Upload, $timeout) {
var vm = this;
vm.postArticle = postArticle;
$scope.$watch('files', function () {
$scope.upload($scope.files);
console.debug("files = ", $scope.files);
//console.debug("upload = ", $scope.upload);
});
$scope.upload = function (files) {
if (files && files.length) {
for (var i = 0; i < files.length; i++) {
var file = files[i];
console.debug("file = ", file, "type = ", file.type);
Upload.upload({
url: '/api/v1/images/',
fields: {
'idArticle': 1,
'article': 1,
'image': file
},
file: file,
image:file
}).progress(function (evt) {
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
$scope.log = 'progress: ' + progressPercentage + '% ' +
evt.config.file.name + '\n' + $scope.log
}).success(function (data, status, headers, config) {
$timeout(function () {
console.log("data === ", data.result);
$scope.log = 'file: ' + config.file.name + ', Response: ' + JSON.stringify(data) + '\n' + $scope.log;
});
});
}
}
};
}
})();
And this line
console.debug("files = ", $scope.files);
prints in the console
type = image/jpeg
The Jade template
form.form-signin(ng-submit="vm.postArticle()" enctype="multipart/form-data")
h2.form-signin-heading Post un article
input.input-block-level(type="text", name="title", placeholder="Title" ng-model="vm.title")
input.input-block-level(type="number", name="price", placeholder="Price" ng-model="vm.price")
input.input-block-level(type="text", name="content", placeholder="Description" ng-model="vm.description")
input.input-block-level(type="number", name="quantity", placeholder="Quantity" ng-model="vm.quantity")
input.input-block-level(type="text", name="color", placeholder="Color" ng-model="vm.color")
input.input-block-level(type="text", name="state", placeholder="State" ng-model="vm.state")
input.input-block-level(type="number", name="year", placeholder="Year" ng-model="vm.year")
p watching model
div(class="button" ngf-select ng-model="files" ngf-multiple="multiple") Select File on file change:
button.btn.btn-large.btn-primary(type="submit") Submit article
Django's REST Framework default way of dealing with uploaded files is to examine the header and decide how to deal with it:
http://www.django-rest-framework.org/api-guide/parsers/#fileuploadparser
By default ng-file-upload, uploads the file in json format, this means that the file you are uploading is submitted as Base64 to Django, which in turn will try to decode the file using the following:
http://www.django-rest-framework.org/api-guide/parsers/#fileuploadparser
This does not work with the default Django File field, there are different ways to accomplish this, one is to use the following option (sendFieldAs) in your request:
Upload.upload({
url: '/api/v1/images/',
...
sendFieldsAs: form,
This will submitt the data as a form, which Django REST framework will process as it should. The other options, would include decoding the Base64 and manually creating the File to be used with the file field, something that adds overhead compared to the sendFieldAs option.

Unable to retrieve HTTP Post data from external API in django

I am getting error : 'str' object has no attribute 'method' . See my code below :
#csrf_exempt
def completepayment(request):
varerr =''
plist = []
if request.method == 'POST':
try:
nid = request.POST['txnref']
except MultiValueDictKeyError:
varerr ="Woops! Operation failed due to server error. Please try again later."
return render(request, 'uportal/main.html', {'varerr':varerr})
# Fetching member details
trym = Transactions.objects.get(TransRef=nid)
amount = trym.Amount
famt = int(amount * 100)
product_id = 48
salt = '4E6047F9E7FDA5638D29FD'
hash_object = hashlib.sha512(str(product_id)+str(nid)+str(famt))
hashed = hash_object.hexdigest()
url = 'https://bestng.com/api/v1/gettransaction.json?productid=pdid&transactionreference=nid&amount=famt'
raw = urllib.urlopen(url)
js = raw.readlines()
#js_object = simplejson.loads(js)
res = simplejson.dumps(js)
for item in res:
rcode = item[0]
#rdesc = item[1]
#preff = item[2]
thisresp = completepayment(rcode)
plist.append(thisresp)
else:
varerr ="Woops! Operation failed due to server error. Please try again later."
return render(request, 'uportal/main.html', {'plist':plist, 'varerr':varerr, 'completepayment':'completepayment'})
In summary I am trying to accept and use HTTP POST value from an external API. Value is showing when I inspect element but DJANGO not retrieving. Please help.
Here is my urls.py
from django.conf.urls import patterns, url
from views import *
from django.views.generic import RedirectView
urlpatterns = patterns('myproject.prelude.views',
# Home:
url(r'^$', 'home', name='home'),
#login
url(r'^login/$', 'login', name='login'),
url(r'^welcome/$', 'welcome', name='welcome'),
# Registration Portal
# Registration Portal
url(r'^uportal/$', 'uportal', name='uportal'),
url(r'^uportal/ugreg/find/$', 'findmember', name='findmember'),
url(r'^uportal/ugreg/search/$', 'searchmember', name='searchmember'),
url(r'^uportal/ugreg/$', 'ugreg', name='ugreg'),
url(r'^uportal/ugreg/initiate-payment/$', 'initiatepayment', name='initiatepayment'),
url(r'^uportal/ugreg/verifypayment/$', 'verifypayment', name='verifypayment'),
url(r'^uportal/ugreg/proceedpayment/$', RedirectView.as_view(url='https://bestng.com/pay'), name='remote_admin'),
url(r'^uportal/ugreg/completepayment/$', completepayment, name='completepayment'),
Thank you
It appears that your problem is that request is an str object rather than a request object.
Please produce urls.py and views.py.
For readability, let’s rewrite the part below:
url = 'https://bestng.com/api/v1/gettransaction.json'
params = '?productid={product_id}&transactionreference={nid}&amount={famt}'
raw = urllib.urlopen(url + params.format(**locals()))
Or even, like so:
url = 'https://bestng.com/api/v1/gettransaction.json'
params = '?productid={product_id}&transactionreference={nid}&amount={famt}'
request = url + params.format(**locals())
raw = urllib.urlopen(request)
Also, the try block is not what I would use. Instead, I would use the get method of the POST dict and return a flag value:
nid = request.POST.get('tnxref', False)
I am unable to reproduce the error you are getting. With a slightly different project-level urls.py (very simplified), the ‘completepayment’ view works fine for me. Here is urls.py.
from django.conf.urls import patterns, url
from app.views import completepayment
# The app is simply called app in my example.
urlpatterns = patterns('',
# I remove the prefix
url(r'^uportal/ugreg/completepayment/$', completepayment, name='completepayment'),
)
# This last parenthesis might be missing in your code.

Categories