node.save with CCK node reference fields

jrbeeman - June 26, 2008 - 00:50
Project:Services
Version:6.x-1.x-dev
Component:Code
Category:bug report
Priority:normal
Assigned:Unassigned
Status:by design
Description

I'm attempting to create a service in Python over XML-RPC for creating nodes that have taxonomy terms and CCK node reference fields. When the taxonomy is set to be "required," I get back a validation error like, " field is required." The node reference field throws a validation error, as well: ": This post can't be referenced."

Any thoughts? Sample code below:

import pprint, sys, time, xmlrpclib;
from config import config;
pp = pprint.PrettyPrinter(indent=2);

def createNode(node):
  return server.node.save(config['key'], sessid, node);

# Make initial connection to service, then login as admin
server = xmlrpclib.Server(config['url']);
connection = server.system.connect(config['key']);
session = server.user.login(config['key'], connection['sessid'], config['username'], config['password']);
sessid = session['sessid'];
user = session['user'];

node = {
  'type': config['node_type'],
  'title': 'python test',
  'body': 'lorem ipsum',
  'uid': user['uid'],
  'name': user['name'],
  'changed': int(time.time()),
  'taxonomy': {
    '1': {'1': 1}
  },
  'field_author': [
    {'value': 'John Doe'},
  ],
  'field_provider': [
    {'nid': 56},
  ],
}

createNode(node);

#1

jrbeeman - June 27, 2008 - 05:08
Title:node.save with taxonomy and CCK node reference fields» node.save with CCK node reference fields

I've figured out the taxonomy issue (seems to have been caused by another incompatible module), but the CCK node reference issue is still giving me problems.

#2

snelson - July 13, 2008 - 04:58
Status:active» postponed (maintainer needs more info)

Any luck on this? The node array is passed straight to drupal_execute, so you're probably just not putting the noderef field in a format drupal_execute likes. You should look at what the $form_values array looks like when the node form is submitted within Drupal, and then match it in your node dict in Python, thought what you posted does look pretty good. You can do this with a hook_form_alter in a custom module.

Scott

#3

jrbeeman - July 16, 2008 - 22:06
Status:postponed (maintainer needs more info)» fixed

Ah - yep, I was just looking at the wrong point in time of the node submission execution. The correct format is:

node = {
  #...
  #...
  'field_provider': {
    'nids': 56,
  },
}

It's just an objects with a property nids (note the plural). Thanks!

#4

Anonymous (not verified) - July 31, 2008 - 04:47
Status:fixed» closed

Automatically closed -- issue fixed for two weeks with no activity.

#5

liquidcms - August 19, 2009 - 21:46
Version:5.x-0.91» 5.x-1.x-dev
Status:closed» active

setting this back to active and up-revving to the release i am using.

I get this same errors when trying to save a node that uses a noderef field. I will go through and check format that you are mentioning here; but even then this should be a bug since i simply do a node_load at master and then send a node.save to slave site with the same node object. Would seem wrong that the format i load isn't valid to be saved.

although maybe this is a core bug and not a services bug - although i coulda swore i do this locally (just load, save with no services involved) all the time; but perhaps not - that will be next test.

#6

liquidcms - August 19, 2009 - 21:52

yup, i think a services bug

this works:

$node = node_load(309473);
unset ($node->nid, $node->vid);
$node->uid = 17;

// local test of noderef save
node_save($node);

and this doesn't

$server = 'http://blogs/services/xmlrpc';
$domain = 'blogm';
$key = 'dcb265af5b6fceba6aa6704fd99754a8';

$anon_session = xmlrpc($server, 'system.connect');

$timestamp = (string) time();

$nonce = user_password();
/*$hash = hash_hmac('sha256', $timestamp .';'.$domain .';'. $nonce .';'.'node.load', $key);
$xmlrpc_result = xmlrpc('http://blogs/services/xmlrpc', 'node.load', $hash, $domain, $timestamp, $nonce, 309470);*/

$hash = hash_hmac('sha256', $timestamp .';'.$domain .';'. $nonce .';'.'user.login', $key);
$xmlrpc_result = xmlrpc($server, 'user.login', $hash, $domain, $timestamp, $nonce, $anon_session['sessid'], 'myuser', 'mypasswd');

if ($xmlrpc_result === FALSE) print '<pre>' . print_r(xmlrpc_error(), TRUE) . '<pre>';
else print '<pre>' . print_r($xmlrpc_result, TRUE) . '<pre>';

$sessid = $xmlrpc_result['sessid'];

$nonce = user_password();
$node = node_load(309473);
unset ($node->nid, $node->vid);
$node->uid = 17;

$hash = hash_hmac('sha256', $timestamp .';'.$domain .';'. $nonce .';'.'node.save', $key);
$xmlrpc_result = xmlrpc($server, 'node.save', $hash, $domain, $timestamp, $nonce, $sessid, $node);

if ($xmlrpc_result === FALSE) print '<pre>' . print_r(xmlrpc_error(), TRUE) . '<pre>';
else print '<pre>' . print_r($xmlrpc_result, TRUE) . '<pre>';

which is basically saying node_save($node) works but node.save($node) doesn't for noderef fields.

#7

heyrocker - August 19, 2009 - 21:52

Note that on the receiving end, the node service uses drupal_execute() to process node saves, not the node_save() function. drupal_execute() mimics a submission of the node/add or node/edit form and there are some cases wherein this data is passed differently than the data you get via a node_load(). Nodereference fields are one instance of this (and these can also depend on which widget is in use.) Taxonomy is another place where this is a problem. There are very good reasons why this is the case and node_save() is not used, although they are kind of obscure and outside the realm of a comment in an issue like this.

#8

heyrocker - August 19, 2009 - 21:54

Also if you are pushing nodes around between sites you may want to check out the deploy module

http://drupal.org/project/deploy

(sorry for the shameless self-promotion)

#9

liquidcms - August 19, 2009 - 21:54

wow.. pretty fast reply.. ok, i'll look at doing node object massaging prior to node.save, to bad that has to occur; but oh well.

btw, i also have taxonomy on this node; and didnt get an error from that; but maybe once i sort uot cleaning noderef fields i will

#10

liquidcms - August 19, 2009 - 21:58

yea, one of the guys on our team has looked at deploy.. don't think it can quite handle all of what we are doing.. which is a very large editorial system that uses workflow and "delivery vehicles" to deliver content to different sites... currently we have email and ftp as delivery vehicle types and i am adding the xmlrpc del veh type to the mix.

perhaps i'll take another look at deploy this evening.. thanks.

#11

liquidcms - August 20, 2009 - 07:06

ahh, and sadly Deploy is only Drupal6 (and we're still on 5)

#12

liquidcms - August 27, 2009 - 10:08

ah, i see.. from #3 above, jrbeeman is only correct if the field is not set to multiple. if it is the format is:

node = {
  #...
  #...
  'field_provider': {
    'nids': {
       0: 56,
       1: 67, 
  },
}

annoying that this isn't consistent.

#13

liquidcms - September 7, 2009 - 23:54

seems like i keep having to write custom field handlers every time i get a bit further on this - first noderef, then taxonomy, then workflow...

then, decided going about this the wrong way.. fix the source rather than each field.. so i made a new node.save service that uses node_save() rather than drupal_execute() and now everything seems much easier.

heyrocker mentions there are reasons for using drupal_execute(); but for my application; haven't seen it yet.

#14

marcingy - September 7, 2009 - 23:57

The simple answer is node_save does not invoke any FAPI based validation while drupal_excute does - and that is why heyrocker correctly says use drupal_excute.

#15

heyrocker - September 8, 2009 - 00:08

The other problem is that if you are using modules that extend the node form through hook_form_alter(), then these additions will get lost when you node save (since typically these are managed through additional submit[] handlers in the form definition.) So for any individual situation where you know what you're dealing with, node_save() will be an option, but for a situation like ours, where we have to worry about a lot of edge cases and dealing with a lot of unkowns, drupal_execute() is the only way to go because it handles things just like Drupal does.

This is in the end a Drupal problem, because the front end (FAPI) is so tightly integrated with backend processing. There are not a lot of clear APIs to bridge the two. There has been a lot of talk over the years about how to address this but nothing has ever really come to light.

#16

liquidcms - September 9, 2009 - 16:59

but still, even with drupal_execute().. special field handlers need to be done for all these "edge cases".

so far, in my app, it is a bit of a toss up:

drupal_execute:
- handles file fields better
- need sep code for taxonomy, noderefs, workflow

node_save:
- need to set fid to 'upload' in file fields
- workflow has to be cleaned up, but easier than with d_execute, just change keys from _workflow to workflow
- taxonomy and noderefs work as is

#17

heyrocker - September 9, 2009 - 17:04

I would argue that the fact that Date and Nodereference need special field handlers that are widget dependent is a bug in those modules, but Karen has made it plain on many occasions that she does not support the drupal_execute() method of saving nodes and that if you want to go that route you are on your own.

You're completely correct that either way you're just choosing your poison.

I had an idea recently that we should supply both options in services, a node_save() version and a drupal_execute() version, and the user can choose which one they want to use. Any comments on this idea? (note that in order to maintain API consistency, I would argue that the exisisting node.save continue to use drupal_execute(), which could be confusing.)

#18

liquidcms - September 10, 2009 - 04:34

well that was the approach i took; so maybe i am biased - but took me about 10 minutes (gotta love drupal) to add a new service that uses node_save().

i guess we'd need to have different method names if both were available; for my app was a no brainer since the service is just part of a much bigger editing and delivery system called CAPSA, so i now have a capsa.node.save and original node.save. Perhaps node.execute (but sadly not very backward compatible)

#19

ray007 - September 10, 2009 - 10:22

@liquidcms: You may not only want to call node_save(), but maybe do a bit more:

<?php
    $n
= node_submit($edit);
   
node_object_prepare($n);
   
$form = array();
   
node_validate($n, $form);
    if (
$errors = form_get_errors()) {
      return
webservices_error(implode("\n", $errors));
    }
   
// TODO: check $form for errors and return them if there are
   
node_save($n);
?>

That should call some of the hooks drupal_execute() also calls, not sure if I missed something.

Would you share the service you've written with us (or me) ?

#20

liquidcms - September 15, 2009 - 07:36

my service is simply a copy of the existing node.save, except it uses node_save():

/**
* Implementation of hook_service().
*/
function capsa_delivery_service() {
  return array(
   
    // Our version of node.save
    array(
      '#method'   => 'capsa.node.save',
      '#callback' => 'capsa_delivery_service_save',
      '#access callback' => 'node_service_save_access',
      '#args'     => array(
        array(
          '#name'         => 'node',
          '#type'         => 'struct',
          '#description'  => t('A node object. Upon creation, node object must include "type". Upon update, node object must include "nid".'))),
      '#return'   => 'struct',
      '#help'     => t('Save a node object into the database using node_save rather than drupal_execute.')
    )
  );
}

/***
* callback for new node.save service which uses node_save() rather than drupal_execute()
*
* @param mixed $edit
*/

function capsa_delivery_service_save($edit) {
  $node = (object)$edit;
  node_save($node);
  if (!is_numeric($node->nid)) {
    return services_error("Node not saved or created.");
  }
  return $node->nid;
}

also, i am using Dr5.

I might get a chance later this week to try out your additions..

thanks.

#21

garrizaldy - October 30, 2009 - 10:27
Version:5.x-1.x-dev» 6.x-1.x-dev

I was able to save and update regular node fields like titles but was unable to save and update on CCK Field on the nodes.

here is my xml request for a node.save after a successful system.connect and user.login http://pastebin.com/m5ceca16

here's an example how I build my node array:

<?php
$node
= array();
$node['title'] = "MyTitle";
$node['field_content'] = array(array("value" => "MyContent"));
?>

#22

heyrocker - November 2, 2009 - 19:29
Status:active» by design

I believe this is a problem with how you are constructing your nodes, and is not a Services issue at all. Please refer to

http://drupal.org/node/439090

for some guidance on how to format nodes for saving thorugh drupal_execute() which the node.save service uses.

Is there still a bug somewhere in this isssue? Because I don't see one. If someone wants to report one then feel free to reopen.

 
 

Drupal is a registered trademark of Dries Buytaert.