Python-Eve: More than one additional lookup - python

Using additional lookups I am able to access a given document of a desired endpoint by a secondary one as stated in the docs:
Besides the standard item endpoint which defaults to
/<resource>/<ID_FIELD_value>, you can optionally define a secondary,
read-only, endpoint like /<resource>/<person_name>. You do so by
defining a dictionary comprised of two items field and url. The former
is the name of the field used for the lookup. If the field type (as
defined in the resource schema) is a string, then you put a URL rule
in url. If it is an integer, then you just omit url, as it is
automatically handled. See the code snippet below for an usage example
of this feature.
So recalling the given example from the docs:
people = {
# 'title' tag used in item links. Defaults to the resource title minus
# the final, plural 's' (works fine in most cases but not for 'people')
'item_title': 'person',
# by default, the standard item entry point is defined as
# '/people/<ObjectId>/'. We leave it untouched, and we also enable an
# additional read-only entry point. This way consumers can also perform
# GET requests at '/people/<lastname>'.
'additional_lookup': {
'url': 'regex("[\w]+")',
'field': 'lastname'
},
# We choose to override global cache-control directives for this resource.
'cache_control': 'max-age=10,must-revalidate',
'cache_expires': 10,
# we only allow GET and POST at this resource endpoint.
'resource_methods': ['GET', 'POST'],
}
Since lastname is set as a secondary endpoint I would be able to access the people endpoint either through the document id (which is default) or the lastname key of a stored document.
Assuming that each person has a unique lastname and a unique nickname. Is there a possibility to define more than one additional lookup in order to have access via the lastname and the nickname?
However, this example is just to show what I am looking for. In my real use case I have a database containing different product information and I want to be able to access those information by both the English and the German title so I can guarantee that all my additional lookups will result in unique document keys.

You can't add more than one additional lookup to the same endpoint. What you can do however, is have multiple endpoints consuming the same datasource.
Multiple API endpoints can target the same database collection. For example you can set both /admins and /users to read and write from the same people collection on the database.
Quote is from the Advanced Datasource Patterns. So you could simply have /books/english/<title> and /books/german/<title>. Both endpoints would still consume the same database collection.
Depending on your schema design you might also go the Sub Resources path and set up something like /products/<language>/books/<title>.

Related

Using XML-RPC in Python 3, how to see if a WordPress post exists by date and category?

I need to query a WordPress site from Python 3 to see if a WordPress post of custom post type with a specific date and category already exists. Based on the docs for the the wp.GetPosts method, this seems to be possible, but I cannot work out the right query. What I'm using currently is:
import xmlrpc.client
wp_url = "http://www.mywebsite.com/xmlrpc.php"
server = xmlrpc.client.ServerProxy(wp_url)
filters = {
'post_date': start_time, # an RFC-3339 formatted date
'post_type' : 'my-custom-post-type',
}
fields = {
'terms' : {
'category': ['my-category']
}
}
post_id = server.wp.getPosts(wp_blogid, wp_username, wp_password, filters, fields)
(where wp_blogid, wp_username and wp_password are all correctly defined, of course).
What this returns is just a list of ten seemingly-random posts of the correct custom post type, with no further filter applied. Do I need to just get every post and then iterate over them for my terms, or is there an easier way?
There is no option to filter posts by date, sorry. The documentation is misleading, in that it claims to support the same filters as #wp.getPost, but the latter has no filtering at all; presumably that section confused fields with filters.
The documentation lists what is supported in the argument lists:
struct filter: Optional.
string post_type
string post_status
int number
int offset
string orderby
string order
The Wordpress XML-RPC source code handling the filters corroborates this.
So using XML-RPC, you have no other option but to page through the results to find the matching posts for the given date.
You should look at the REST API instead, the /wp-json/wp/v2/posts route lets you filter posts by date.

Python-Eve: Prevent inserting duplicates without using unique fields

I am trying to prevent inserting duplicate documents by the following approach:
Get a list of all documents from the desired endpoint which will contain all the documents in JSON-format. This list is called available_docs.
Use a pre_POST_<endpoint> hook in order to handle the request before inserting to the data. I am not using the on_insert hook since I need to do this before validation.
Since we can access the request object use request.json to get the payload JSON-formatted
Check if request.json is already contained in available_docs
Insert new document if it's not a duplicate only, abort otherwise.
Using this approach I got the following snippet:
def check_duplicate(request):
if not request.json in available_sims:
print('Not a duplicate')
else:
print('Duplicate')
flask.abort(422, description='Document is a duplicate and already in database.')
The available_docs list looks like this:
available_docs = [{'foo': ObjectId('565e12c58b724d7884cd02bb'), 'bar': [ObjectId('565e12c58b724d7884cd02b9'), ObjectId('565e12c58b724d7884cd02ba')]}]
The payload request.json looks like this:
{'foo': '565e12c58b724d7884cd02bb', 'bar': ['565e12c58b724d7884cd02b9', '565e12c58b724d7884cd02ba']}
As you can see, the only difference between the document which was passed to the API and the document already stored in the DB is the datatype of the IDs. Due to that fact, the if-statement in my above snippet evaluates to True and judges the document to be inserted not being a duplicate whereas it definitely is a duplicate.
Is there a way to check if a passed document is already in the database? I am not able to use unique fields since the combination of all document fields needs to be unique only. There is an unique identifier (which I left out in this example), but this is not suitable for the desired comparison since it is kind of a time stamp.
I think something like casting the given IDs at the keys foo and bar as ObjectIDs would do the trick, but I do not know how to to this since I do not know where to get the datatype ObjectID from.
You approach would be much slower than setting a unique rule for the field.
Since, from your example, you are going to compare objectids, can't you simply use those as the _id field for the collection? In Mongo (and Eve of course) that field is unique by default. Actually, you typically don't even define it. You would not need to do anything at all, as a POST of a document with an already existing id would fail right away.
If you can't go that way (maybe you need to compare a different objectid field and still, for some reason, you can't simply set a unique rule for the field), I would look at querying the db for the field value instead than getting all the documents from the db and then scanning them sequentially in code. Something like db.find({db_field: new_document_field_value}). If that returns true, new document is a duplicate. Make sure db_field is indexed (which usually holds true also for fields tagged with unique rule)
EDIT after the comments. A trivial implementation would probable be something like this:
def pre_POST_callback(resource, request):
# retrieve mongodb collection using eve connection
docs = app.data.driver.db['docs']
if docs.find_one({'foo': <value>}):
flask.abort(422, description='Document is a duplicate and already in database.')
app = Eve()
app.run()
Here's my approach on preventing duplicate records:
def on_insert_subscription(items):
c_subscription = app.data.driver.db['subscription']
user = decode_token()
if user:
for item in items:
if c_subscription.find_one({
'topic': ObjectId(item['topic']),
'client': ObjectId(user['user_id'])
}):
abort(422, description="Client already subscribed to this topic")
else:
item['client'] = ObjectId(user['user_id'])
else:
abort(401, description='Please provide proper credentials')
What I'm doing here is creating subscriptions for clients. If a client is already subscribed to a topic I throw 422.
Note: the client ID is decoded from the JWT token.

find_and_modify with upsert using Python-EVE

There is common use case when you need update or insert. For instance:
obj = db['data'].find_and_modify(
{
'Name': data['Name'],
'SourcePage': data['SourcePage'],
},
data,
upsert=True
)
Of course can split this request into GET and then PATCH or INSERT but maybe there is better way?
P.S. eve provides some nice features like document versions and meta data (_created, _updated etc.)
upsert support is now part of the upcoming release.
One doesn't have to do anything different. The feature is "turned on" by default. So if a user tries to PUT an item that does not exist, a new item will be created. The id field sent in the payload is ignored.
If a user does not want this feature, the user needs to explicitly set UPSERT_ON_PUT to False. Now, the user gets the "old" behaviour back. i.e when the user tries to PUT non-existing item, 404 is returned.

HP Quality Center field names

I am interacting with HP QC using python and referring to HP ALM OTA documentation.
What I need is to access fields in different places (particularly now I am trying to access Test Set description field). As far as I know it is done by following: TestSet['description field name'] = 'I am description' The problem is - I don't know this field name and I can't find it in documentation mentioned.Up until now I was wondering around examples in hope to find these names (the way I found that Actual field in test step is named 'ST_ACTUAL').
Could you please help me find some kind of list of these field names. Or the way to retrieve them.. (Or at least give me the name of this Test Set description field)
When you get an entity field value, the field must be the underlying database column name for that entity. You can discover this using the project customization UI in HP ALM: select project entities then explore the system or user fields. Beware that the Design Step says the column name begins ST_... it doesn't. It's actually DS_...
You can also get this information programmatically. Given a factory instance use the equivalent of:
private void ExploreFactoryFieldDefinitions(IBaseFactory factory)
{
List fields = factory.Fields;
foreach (TDField field in fields)
{
FieldProperty field_property = (FieldProperty)field.Property;
if (field_property.IsRequired)
{
Log(String.Format("User Label: {0}\n", field_property.UserLabel));
Log(String.Format("User Column Type: {0}\n", field_property.UserColumnType));
Log(String.Format("DB Column Name: {0}\n", field_property.DBColumnName));
Log(String.Format("DB Column Type: {0}\n", field_property.DBColumnType));
Log(String.Format("DB Table Name: {0}\n", field_property.DBTableName));
}
}
}
field_property.UserLabel gives you the user friendly field name. field_property.DBColumn name gives you the database column name that should be used with entity[field_name].
BTW - don't forget to call entity.Post() to have your changes saved. When working with a versioned project you have a few more hoops to jump through too. Good luck!
I think, the field you are looking for is CY_COMMENT (hint). Maybe there is a better way—but you can find the names of the fields in the Query Builder. If you create an Excel Report and open the Query Builder, there is an Entities View which shows all the fields of the tables (even the user-defined fields). Maybe there is some kind of database documentation which gives you the same thing.

OpenERP 7: relational fields in client-side domain filters

here shows my error
2013-04-23 05:36:03,877 17001 ERROR demo openerp.sql_db: bad query: SELECT "res_company".id FROM "res_company" WHERE "res_company".id = 'deduction_id.bpl_company_id.id' ORDER BY "res_company"."name"
Traceback (most recent call last):
File "/home/bellvantage/Documents/openerp-7.0/openerp-7/openerp/sql_db.py", line 226, in execute
res = self._obj.execute(query, params)
DataError: invalid input syntax for integer: "deduction_id.bpl_company_id.id"
LINE 1: ...y".id FROM "res_company" WHERE "res_company".id = 'deduction...
^
2013-04-23 05:36:03,878 17001 ERROR demo openerp.osv.osv: Uncaught exception
Traceback (most recent call last):
here shows my code which i trying to add domain filter
class estate_bank_deductions(osv.osv):
_name = 'bpl.estate.bank.deductions'
_description = 'Estate Bank Deductions'
_columns = {
'deduction_id':fields.many2one('bpl.deduction.estate.data', 'Bank Deductions', ondelete='cascade'),
'name': fields.many2one('bpl.deduction.registration', 'Deduction', domain="[('type','=','bank'),('bpl_company_id.id','=','deduction_id.bpl_company_id.id')]"),
'bank_id': fields.many2one('bpl.bank.registration', 'Bank Name'),
'branch_id': fields.many2one('bpl.branch.registration', 'Branch'),
}
here is part of my parent class of estate bank deduction class
_name = 'bpl.deduction.estate.data'
_description = 'BPL Deduction Estate Data'
_columns = {
'bpl_company_id':fields.many2one('res.company', 'Company', help='Company'),
please help me to sort out this.?
is there anything missing in my domain filtering mechanism or is that way is incorrect.?
Dear odony,
one more thing to clarify,
now need to define my field as fields.function ?
but the issue is after adding the domain filter also other irrelevant records also loaded to my drop down (after i added the widget="selection" attribute).
then how to restrict them. i already posted that issue
hope you will advice me on this...again thanks u soo much
It won't work either way. Model fields support only two kind of domain filters:
Server-side (list) domains, specified as a list of tuples: can only be static and are meant to be used for one2many and many2many fields mostly. These filters will be applied on the server-side when reading the list of values for the field, and will never be used on the client-side. The domain can only contain constants on the right side of each domain element. An example of a valid server-side domain is:
# filter deductions based on an imaginary "confirmed" boolean field
deduction_ids = fields.one2many('bpl.estate.bank.deductions', 'bank_id',
string="Confirmed Deductions",
domain=[('confirmed', '=', True)])
Client-side (string) domains, specified as the string representation of a server-side domain: can be static or dynamic, and are meant to be used for many2one fields mostly, and never evaluated on the server-side. They are simply copied in any view where the field is used, and interpreted on the client-side to filter the list of available choices for that field. The right hand side of each domain element can be made dynamic by referring to the name of any other field included in the view, and will be replaced by the field value when the domain is evaluated. The field value will be returned in the same format that it would be passed to write() when saving the changes. An example of a valid client-side domain is:
# only allow choosing a branch that belongs to the right bank registration
# (here `branch_id` refers to the current value of the `branch_id` field
# in the form view
'branch_id': fields.many2one('bpl.branch.registration', 'Branch',
domain="[('branch_id','=',branch_id)]")
UPDATE: watch out, you cannot use widget="selection" in combination with client-side domains because this option converts your many2one field into a fake fields.selection whose options are statically evaluated server-side. In this case your client-side domain will be ignored and the list of available values will never change. Now if you simply want to avoid users creating new values you can restrict access rights to prevent that, and if you want to avoid showing an icon to view/edit the target object you can add options='{"no_open": True}' to the field in the form view.
Problem
In your case it seems you want to use a client-side domain to filter based on the current value of the deduction_id field. But your domain expression ('bpl_company_id.id','=','deduction_id.bpl_company_id.id') is incorrect:
The left hand side must refer to a field or a field path, and the .id suffix is useless here, bpl_company_id is sufficient to filter on that many2one field.
The right hand side can be dynamic or static, depending if you make it a constant or a variable. If you quote the value as 'deduction_id.bpl_company_id.id' then you're comparing the m2o ID (or the m2o name if your remove the .id suffix) to the literal string value "deduction_id.bpl_company_id.id", certainly not what you want.
If you remove the quoting as suggested by user2310008 you will make it dynamic indeed, but the value of the deduction_id will be the ID of the selected "deduction"! You cannot treat it as a browse_record (ActiveRecord-like object) like on the server-side, so when you do "deduction_id.bpl_company_id.id" you'll get an error because an integer has no bpl_company_id attribute.
Solution
The usual way to implement this kind of filtering is to add an on_change method on the deduction_id field, and use it to dynamically alter the domain of the name column, via the domain key that onchange methods can return. Something like this:
<!-- in the XML view -->
<field name="deduction_id" on_change="onchange_deduction_id(deduction_id)"/>
<field name="name"/>
# in the python model
def onchange_deduction_id(self, cr, uid, ids, deduction_id, context=None):
if deduction_id:
deduction = self.pool['bpl.deduction.estate.data'].browse(cr, uid,
deduction_id,
context)
return {'domain': {'name': [('bpl_company_id', '=',
deduction.bpl_company_id.id)]}
return {} # or perhaps a default domain?
Try this.
('bpl_company_id','=',deduction_id.bpl_company_id.id)
Without quotes the second part, and the field can not be done recursively.

Categories