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!
Related
Last week I posted a kind of vague question as I was trying to join data from an external Rest API with a local SQLAlchemy schema. Unsurprisingly, I didn't get a lot of responses and after some experimentation with parsing the response json into a temporary table I've decided to move away from this approach and add a new resolver for the external API that can be called separately from the resolver for SQLAlchemy. This is what I have so far for the querying the external API:
class Red(graphene.ObjectType):
redName = graphene.String()
accessLevel = graphene.String()
numBytes = graphene.Int()
class Query(graphene.ObjectType):
red = graphene.Field(Red, redName = graphene.String(required=True))
def resolve_red(self, info, **kwargs):
dataset_Red = <custom library for querying API>.dataset(kwargs['redName'])
dataset_Red.get()
resp = dataset_Red.properties
return json.dumps(resp)
GraphiQL recognizes my new resolver and will run the query with no errors but it returns no data. For example, if this is my query:
query{red(redName:"<dataset_name>") {
accessLevel
numBytes
}
}
I get the following response:
{
"data": {
"red": {
"accessLevel": null,
"numBytes": null
}
}
}
What did I miss? I'm thinking there's an issue with class definition. Can someone show me what I did wrong?
I think you should return Red type from resolver resolve_red instead of json dumped string:
class Red(graphene.ObjectType):
redName = graphene.String()
accessLevel = graphene.String()
numBytes = graphene.Int()
class Query(graphene.ObjectType):
red = graphene.Field(Red, redName = graphene.String(required=True))
def resolve_red(self, info, **kwargs):
dataset_Red = <custom library for querying API>.dataset(kwargs['redName'])
dataset_Red.get()
resp = dataset_Red.properties
return Red(
redName=resp.get('redName'),
accessLevel=resp.get('accessLevel'),
NumBytes=resp.get('numBytes')
)
Or assuming fields in response are the same as Red attributes:
class Red(graphene.ObjectType):
redName = graphene.String()
accessLevel = graphene.String()
numBytes = graphene.Int()
class Query(graphene.ObjectType):
red = graphene.Field(Red, redName = graphene.String(required=True))
def resolve_red(self, info, **kwargs):
dataset_Red = <custom library for querying API>.dataset(kwargs['redName'])
dataset_Red.get()
resp = dataset_Red.properties
return Red(**resp)
Sound I found a solution that seems to work. The issue was the response type, I had to serialize and then deserialize it for flask (obvious right?).
I added this two functions:
def _json_object_hook(d):
return namedtuple('X', d.keys())(*d.values())
def json2obj(data):
return json.loads(data, object_hook=_json_object_hook)
And then changed my resolver to call json2obj on the response:
def resolve_red(self, info, **kwargs):
dataset_Red = <custom library for querying API>.dataset(kwargs['redName'])
dataset_Red.get()
resp = dataset_Red.properties
return json2obj(json.dumps(resp))
I found this solution in another stackoverflow post but now I can't find the original. If anyone else finds it, please add a link in the comments and I'll add it to this answer.
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.
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,)
How to change the printed qweb filename depends on the source document field name?
Im in the model stock.picking
So when i click the print -> Delivery note, then the qweb will be printed, but te filename will be depends on the Source document field.
Here i present the picture that explain what i mean.
EXAMPLE
You can give dynamic report name using configuration, but it will apply when you print one report.
Below is the example to Print custom name in report.Create one field in ir.actions.report.xml, in which user can configure report name.
from openerp import models, fields
class IrActionsReportXml(models.Model):
_inherit = 'ir.actions.report.xml'
download_filename = fields.Char(
'Download filename')
Now you need to create two files.
Report Controller
from openerp import http
from openerp.addons.mail.models import mail_template
from openerp.addons.report.controllers.main import ReportController
from openerp.addons.web.controllers.main import content_disposition
class ReportController(ReportController):
#http.route([
'/report/<path:converter>/<reportname>',
'/report/<path:converter>/<reportname>/<docids>',
])
def report_routes(self, reportname, docids=None, converter=None, **data):
response = super(ReportController, self).report_routes(
reportname, docids=docids, converter=converter, **data)
if docids:
docids = [int(i) for i in docids.split(',')]
report_xml = http.request.session.model('ir.actions.report.xml')
report_ids = report_xml.search(
[('report_name', '=', reportname)])
for report in report_xml.browse(report_ids):
if not report.download_filename:
continue
objects = http.request.session.model(report.model)\
.browse(docids or [])
generated_filename = mail_template.mako_template_env\
.from_string(report.download_filename)\
.render({
'objects': objects,
'o': objects[:1],
'object': objects[:1],
'ext': report.report_type.replace('qweb-', ''),
})
response.headers['Content-Disposition'] = content_disposition(
generated_filename)
return response
#http.route(['/report/download'])
def report_download(self, data, token):
response = super(ReportController, self).report_download(data, token)
# if we got another content disposition before, ditch the one added
# by super()
last_index = None
for i in range(len(response.headers) - 1, -1, -1):
if response.headers[i][0] == 'Content-Disposition':
if last_index:
response.headers.pop(last_index)
last_index = i
return response
2.Report.py
import json
from openerp import http
from openerp.addons.web.controllers import main
from openerp.addons.mail.models import mail_template
class Reports(main.Reports):
#http.route('/web/report', type='http', auth="user")
#main.serialize_exception
def index(self, action, token):
result = super(Reports, self).index(action, token)
action = json.loads(action)
context = dict(http.request.context)
context.update(action["context"])
report_xml = http.request.env['ir.actions.report.xml']
reports = report_xml.search([
('report_name', '=', action['report_name']),
('download_filename', '!=', False)])
for report in reports:
objects = http.request.session.model(context['active_model'])\
.browse(context['active_ids'])
generated_filename = mail_template.mako_template_env\
.from_string(report.download_filename)\
.render({
'objects': objects,
'o': objects[0],
'object': objects[0],
})
result.headers['Content-Disposition'] = main.content_disposition(
generated_filename)
return result
Odoo community Providing us a default module for report custom name. you can directly install this module and set report name like : ${o.name}
Here o means your record.
Below is a link of odoo community module.
https://www.odoo.com/apps/modules/9.0/report_custom_filename/
This may help you.
try attachment in your reprot to change report file name as your desired name.
<report
string="Delivery Slip"
id="action_report_delivery"
model="stock.picking"
report_type="qweb-pdf"
name="stock.report_deliveryslip"
file="stock.report_deliveryslip"
attachment="'Custom Text...'+'.pdf')"
/>
Here is my code which works for different reportname in the same model. So the filename only changes for the certain filename.
>
from openerp.addons.web.http import Controller, route, request
from openerp.addons.web.controllers.main import _serialize_exception
from openerp.osv import osv
from openerp.addons.report.controllers.main import ReportController
from openerp import http
import simplejson
import logging
class SponsorReportController(ReportController):
#route(['/report/download'], type='http', auth="user")
def report_download(self, data, token):
requestcontent = simplejson.loads(data)
url, type = requestcontent[0], requestcontent[1]
logging.info(url)
logging.info(type)
response = ReportController().report_download(data, token)
if len(url.split('/report/pdf/')) > 1 and type == 'qweb-pdf':
reportname = url.split('/report/pdf/')[1].split('?')[0]
reportname, docids = reportname.split('/')
logging.info(reportname)
assert docids
logging.info(docids)
if reportname == 'pci_stock_picking_report.report_delivery_note':
partner_obj = http.request.env['stock.picking']
object = partner_obj.browse(int(docids))
filename = "No."+(object.origin)
response.headers.set('Content-Disposition', 'attachment; filename=%s.pdf;' % filename)
return response
I am trying to query a unique document using its ObjectId. However the error comes up:
DoesNotExist: Route matching query does not exist
When, upon passing this to my view as request, it prints out the corresponding ObjectId in ObjectId typeform. Therefore there shouldn't be a problem with the line route_test = Route.objects.get(id=_id).
I have the following code:
views.py
def update(request):
if request.method == "POST":
_id = request.POST.get('_id',ObjectId())
print(_id)
route_id = request.POST.get('route_id','')
geometry = request.POST.get('geometry', '')
properties = request.POST.get('properties','')
#r = Route.objects.get(route_id='LTFRB_PUJ2616') --> I cannot use this
#because it has 5 instances (Not Unique)
#print (r["id"],r["properties"])
test = Route.objects.get(id = ObjectId('587c4c3b203ada19e8e0ecf6'))
print (test["id"], test["properties"])
try:
route_test = Route.objects.get(id=_id)
print(route_test)
Route.objects.get(id=_id).update(set__geometry=geometry, set__properties=properties)
return HttpResponse("Success!")
except:
return HttpResponse("Error!")
ajax
var finishBtn = L.easyButton({
id:'finish',
states: [{
icon:"fa fa-check",
onClick: function(btn){
selectedFeature.editing.disable();
layer.closePopup();
var editedFeature = selectedFeature.toGeoJSON();
alert("Updating:" + editedFeature.route_id);
$.ajax({
url: "/update/",
data: {id:editedFeature.id,
route_id: JSON.stringify(editedFeature.route_id),
geometry: JSON.stringify(editedFeature.geometry),
properties: JSON.stringify(editedFeature.properties)
},
type: 'POST'
});
}
model.py
from __future__ import unicode_literals
from mongoengine import *
class Route(Document):
type = StringField(required=True)
route_id = StringField(required=True)
geometry = LineStringField()
properties = DictField()
meta = {'collection':'routes'}
What should be done? Even the line test = Route.objects.get(id = ObjectId('587c4c3b203ada19e8e0ecf6')) where I directly supplied the incoming _id has the same error...