Submitted by Adriana Knouf on
"Many":http://www.readwriteweb.com/archives/deliciouss_data_policy_is_like_setting_a_museum_on.php have "written about":http://www.salon.com/technology/silicon_valley/index.html?story=/tech/dan_gillmor/2010/12/17/yahoo_shuttering_bookmarks_service the impending demise of "delicious":http://delicious.com, one of the first so-called "Web 2.0" companies, and one of the few cloud-based services that I actually use. delicious was useful because of its simplicity, with a pared-down interface, clean bookmarklets or browser plugins, and the ability to easily tag new bookmarks. I used to use it extensively to find new sites, but stopped doing that a few years ago. These days it was basically a way to store bookmarks in a place that was accessible anywhere I had an internet connection.
But as we have expected for a while, and are witnessing at the moment, the cloud is disappearing, its solidity nothing but a mirage. We are in for trouble in the future, methinks, because so many institutions, "including universities":http://www.hastac.org/blogs/nknouf/wikileaks-broadcast-internet-and-importance-new-media-assemblages, have moved their online activities from local hosting to this mythical "cloud".
The point of this post is not to go into the deeper issues here, but to explain, in some detail, how you can setup a delicious-like interface on your own server. I'm going to be using "Drupal":http://drupal.org, one of the most well-known free software content management systems (CMS). Doing this requires a certain amount of technical ability, and I won't be able to go into that here. But if you're interested, read on.
h2. Creating the content type
While there are Drupal modules for creating "weblinks":http://drupal.org/project/weblinks, in my brief testing I found them to be both too cumbersome, and too limited for what I wanted to do. In any event I wanted to see how easy it might be to do this using the tools Drupal provides.
Turns out that we can easily create a Bookmarks type using CCK. The two additional fields I created are for the "URL" and a "Post Date" (so that we can ensure we have the correct dates for the bookmarks when we import our data below). The first is of type "text" and the second is of type "date". The exported CCK code can be found below.
*Update*: By using the "Unique Field":http://drupal.org/project/unique_field module, we can require that the URL is unique, redirecting you to the existing bookmark if desired. This helps prevent posting of identical bookmarks, and might remind you to look through your bookmarks if you keep saving things that you've already saved...not that that just happened to me.
h2. Taxonomies
I decided to create a separate vocabulary for my bookmarks tags, and I thus edited this vocabulary to allow editing by the Bookmarks content type. Make a note of the vocabulary ID, as we will need this later.
h2. Services
To easily import our bookmarks, it's best to use Drupal's "Services":http://drupal.org/project/services module. Using services you can add nodes, get taxonomy items, and so on using XML-RPC or JSON. Setting up and configuring Services is beyond the scope of this post, but the documentation is actually fairly good.
h2. Delicious import
Next, we need to import our bookmarks. To begin, we need to _export_ our bookmarks from Delicious. The code to this, on Mac OS X or Linux, is:
where <username> is your Delicious username and <password> is your Delicious password. Your bookmarks will then be downloaded and saved in the @bookmarks.xml@ file.
We're now ready to parse this XML file and post it to our new Bookmarks content type. I wrote a dirty python script called @parseDelicious.py@ to do this; you can find the link to it at the bottom of the page. It requires the @lxml@ library for @etree@. The script is probably highly inefficient, especially in the parts that check whether or not existing tags are in your Drupal taxonomy. The script requires a number of options that are explained by calling it with the @-h@ switch. To run:
Here <server> is the hostname of the Drupal server (*without* @/services/xmlrpc@), <username> is the username of a user on the Drupal server, <password> is your password, <vid> is the numeric vocabulary ID of the desired vocabulary for your Delicious tags, and <bookmarks xml> is the path to the XML file just downloaded. Depending on how many bookmarks you have, this might take a _long_ time.
h2. Creating a View of your Bookmarks
Next, let's create a simple view that will give us our bookmarks in a format that is close to that of Delicious. Here we use the Views module, as well as Semantic Views to allow us to style the resulting data. I've designed the view to make it easy to style in CSS. Basically the logic is that we pull the node title, the CCK post date, the CCK URL, the node body, and the taxonomy terms. We then link the node title to the CCK URL, create useful classes on each element, setup a pager, and set our CSS classes. The is at the bottom of the page.
It is also possible to create a tag cloud of all our bookmark tags using the Tagadelic module.
h2. Creating a bookmarklet for posting
Finally, we can create a simple bookmarklet for posting to your site. To do this we need the "Prepopulate":http://drupal.org/project/prepopulate module. The bookmarklet code below is modified slightly from the example given in the documentation for the module.
notextile.
Simple change "example.com" to the name of your host. The names of the values are based off of the content type we created above.
You can see the bookmarks that I've imported from Delicious, with minimal CSS formatting, "here":http://zeitkunst.org/bookmarks.
h2. Where's the social?
Of course this method means that we only host the bookmarks on our own site, thus losing all of the social capabilities of delicious. The challenge for the future, given the disappearance of the cloud, is how to store data locally, but be able to access it distributively. There is no reason that something like Delicious could not be implemented using a "distributed hash table":https://secure.wikimedia.org/wikipedia/en/wiki/Distributed_hash_table, thus preventing any one company from being a source of failure. It seems like it would be possible, then, to write a program that would store bookmarks as hashes in this table, and then build on top of that the necessary metadata such as tags, users, groups, etc. This would parallel the development of "thimbl":http://www.thimbl.net/, using as well public-key encryption to provide security. But this is a project for more than a single day; if you are interested, please let me know.
h2. Code
h3. Bookmarks Content Type
notextile.
h3. Bookmarks View
notextile.
h3. parseDelicious.py
notextile.
curl https://<username>:<password>@api.del.icio.us/v1/posts/all > bookmarks.xml
python parseDelicious.py -s <server> -u <username> -p <password> -v <vid> -f <bookmarks xml>
javascript:u=document.location.href;t=document.title;s=window.getSelection();
void(window.open(%22http://example.com/node/add/bookmarks?edit[title]=%22+escape(t)+
'&edit[body_filter][body]='+escape(s)+'&edit[field_url][0][value]='+escape(u),'_blank',
'width=1024,height=500,status=yes,resizable=yes,scrollbars=yes'));
void(window.open(%22http://example.com/node/add/bookmarks?edit[title]=%22+escape(t)+
'&edit[body_filter][body]='+escape(s)+'&edit[field_url][0][value]='+escape(u),'_blank',
'width=1024,height=500,status=yes,resizable=yes,scrollbars=yes'));
$content['type'] = array (
'name' => 'Bookmarks',
'type' => 'bookmarks',
'description' => 'Trying to be a delicious replacement...',
'title_label' => 'Name',
'body_label' => 'Notes',
'min_word_count' => '0',
'help' => '',
'node_options' =>
array (
'status' => true,
'promote' => true,
'sticky' => false,
'revision' => false,
),
'upload' => '1',
'pingback' => '0',
'community_tags_display' => '1',
'old_type' => 'bookmarks',
'orig_type' => '',
'module' => 'node',
'custom' => '1',
'modified' => '1',
'locked' => '0',
'content_profile_use' => 0,
'comment' => '0',
'comment_default_mode' => '4',
'comment_default_order' => '1',
'comment_default_per_page' => '50',
'comment_controls' => '3',
'comment_anonymous' => '0',
'comment_subject_field' => '1',
'comment_preview' => '1',
'comment_form_location' => '0',
'ant' => '0',
'ant_pattern' => '',
'ant_php' => 0,
);
$content['fields'] = array (
0 =>
array (
'label' => 'Post date',
'field_name' => 'field_post_date',
'type' => 'date',
'widget_type' => 'date_popup',
'change' => 'Change basic information',
'weight' => '-4',
'default_value' => 'now',
'default_value2' => 'same',
'default_value_code' => '',
'default_value_code2' => '',
'input_format' => 'j F Y - g:ia',
'input_format_custom' => '',
'year_range' => '-3:+3',
'increment' => '1',
'advanced' =>
array (
'label_position' => 'above',
'text_parts' =>
array (
'year' => 0,
'month' => 0,
'day' => 0,
'hour' => 0,
'minute' => 0,
'second' => 0,
),
),
'label_position' => 'above',
'text_parts' =>
array (
),
'description' => '',
'group' => false,
'required' => 1,
'multiple' => '0',
'repeat' => 0,
'todate' => '',
'granularity' =>
array (
'year' => 'year',
'month' => 'month',
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
'second' => 'second',
),
'default_format' => 'medium',
'tz_handling' => 'site',
'timezone_db' => 'UTC',
'op' => 'Save field settings',
'module' => 'date',
'widget_module' => 'date',
'columns' =>
array (
'value' =>
array (
'type' => 'varchar',
'length' => 20,
'not null' => false,
'sortable' => true,
'views' => true,
),
),
'display_settings' =>
array (
'label' =>
array (
'format' => 'above',
'exclude' => 0,
),
'teaser' =>
array (
'format' => 'default',
'exclude' => 0,
),
'full' =>
array (
'format' => 'default',
'exclude' => 0,
),
4 =>
array (
'format' => 'default',
'exclude' => 0,
),
2 =>
array (
'format' => 'default',
'exclude' => 0,
),
3 =>
array (
'format' => 'default',
'exclude' => 0,
),
'token' =>
array (
'format' => 'default',
'exclude' => 0,
),
),
),
1 =>
array (
'label' => 'URL',
'field_name' => 'field_url',
'type' => 'text',
'widget_type' => 'text_textfield',
'change' => 'Change basic information',
'weight' => '-3',
'rows' => 5,
'size' => '60',
'description' => '',
'default_value' =>
array (
0 =>
array (
'value' => 'http://',
'_error_element' => 'default_value_widget][field_url][0][value',
),
),
'default_value_php' => '',
'default_value_widget' =>
array (
'field_url' =>
array (
0 =>
array (
'value' => 'http://',
'_error_element' => 'default_value_widget][field_url][0][value',
),
),
),
'group' => false,
'required' => 1,
'multiple' => '0',
'text_processing' => '0',
'max_length' => '',
'allowed_values' => '',
'allowed_values_php' => '',
'op' => 'Save field settings',
'module' => 'text',
'widget_module' => 'text',
'columns' =>
array (
'value' =>
array (
'type' => 'text',
'size' => 'big',
'not null' => false,
'sortable' => true,
'views' => true,
),
),
'display_settings' =>
array (
'label' =>
array (
'format' => 'above',
'exclude' => 0,
),
5 =>
array (
'format' => 'default',
'exclude' => 0,
),
'teaser' =>
array (
'format' => 'default',
'exclude' => 0,
),
'full' =>
array (
'format' => 'default',
'exclude' => 0,
),
4 =>
array (
'format' => 'default',
'exclude' => 0,
),
2 =>
array (
'format' => 'default',
'exclude' => 0,
),
3 =>
array (
'format' => 'default',
'exclude' => 0,
),
'token' =>
array (
'format' => 'default',
'exclude' => 0,
),
),
),
);
$content['extra'] = array (
'title' => '-5',
'body_field' => '-1',
'revision_information' => '1',
'author' => '0',
'options' => '2',
'comment_settings' => '7',
'menu' => '6',
'taxonomy' => '-2',
'path' => '5',
'attachments' => '4',
'path_redirect' => '3',
);
'name' => 'Bookmarks',
'type' => 'bookmarks',
'description' => 'Trying to be a delicious replacement...',
'title_label' => 'Name',
'body_label' => 'Notes',
'min_word_count' => '0',
'help' => '',
'node_options' =>
array (
'status' => true,
'promote' => true,
'sticky' => false,
'revision' => false,
),
'upload' => '1',
'pingback' => '0',
'community_tags_display' => '1',
'old_type' => 'bookmarks',
'orig_type' => '',
'module' => 'node',
'custom' => '1',
'modified' => '1',
'locked' => '0',
'content_profile_use' => 0,
'comment' => '0',
'comment_default_mode' => '4',
'comment_default_order' => '1',
'comment_default_per_page' => '50',
'comment_controls' => '3',
'comment_anonymous' => '0',
'comment_subject_field' => '1',
'comment_preview' => '1',
'comment_form_location' => '0',
'ant' => '0',
'ant_pattern' => '',
'ant_php' => 0,
);
$content['fields'] = array (
0 =>
array (
'label' => 'Post date',
'field_name' => 'field_post_date',
'type' => 'date',
'widget_type' => 'date_popup',
'change' => 'Change basic information',
'weight' => '-4',
'default_value' => 'now',
'default_value2' => 'same',
'default_value_code' => '',
'default_value_code2' => '',
'input_format' => 'j F Y - g:ia',
'input_format_custom' => '',
'year_range' => '-3:+3',
'increment' => '1',
'advanced' =>
array (
'label_position' => 'above',
'text_parts' =>
array (
'year' => 0,
'month' => 0,
'day' => 0,
'hour' => 0,
'minute' => 0,
'second' => 0,
),
),
'label_position' => 'above',
'text_parts' =>
array (
),
'description' => '',
'group' => false,
'required' => 1,
'multiple' => '0',
'repeat' => 0,
'todate' => '',
'granularity' =>
array (
'year' => 'year',
'month' => 'month',
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
'second' => 'second',
),
'default_format' => 'medium',
'tz_handling' => 'site',
'timezone_db' => 'UTC',
'op' => 'Save field settings',
'module' => 'date',
'widget_module' => 'date',
'columns' =>
array (
'value' =>
array (
'type' => 'varchar',
'length' => 20,
'not null' => false,
'sortable' => true,
'views' => true,
),
),
'display_settings' =>
array (
'label' =>
array (
'format' => 'above',
'exclude' => 0,
),
'teaser' =>
array (
'format' => 'default',
'exclude' => 0,
),
'full' =>
array (
'format' => 'default',
'exclude' => 0,
),
4 =>
array (
'format' => 'default',
'exclude' => 0,
),
2 =>
array (
'format' => 'default',
'exclude' => 0,
),
3 =>
array (
'format' => 'default',
'exclude' => 0,
),
'token' =>
array (
'format' => 'default',
'exclude' => 0,
),
),
),
1 =>
array (
'label' => 'URL',
'field_name' => 'field_url',
'type' => 'text',
'widget_type' => 'text_textfield',
'change' => 'Change basic information',
'weight' => '-3',
'rows' => 5,
'size' => '60',
'description' => '',
'default_value' =>
array (
0 =>
array (
'value' => 'http://',
'_error_element' => 'default_value_widget][field_url][0][value',
),
),
'default_value_php' => '',
'default_value_widget' =>
array (
'field_url' =>
array (
0 =>
array (
'value' => 'http://',
'_error_element' => 'default_value_widget][field_url][0][value',
),
),
),
'group' => false,
'required' => 1,
'multiple' => '0',
'text_processing' => '0',
'max_length' => '',
'allowed_values' => '',
'allowed_values_php' => '',
'op' => 'Save field settings',
'module' => 'text',
'widget_module' => 'text',
'columns' =>
array (
'value' =>
array (
'type' => 'text',
'size' => 'big',
'not null' => false,
'sortable' => true,
'views' => true,
),
),
'display_settings' =>
array (
'label' =>
array (
'format' => 'above',
'exclude' => 0,
),
5 =>
array (
'format' => 'default',
'exclude' => 0,
),
'teaser' =>
array (
'format' => 'default',
'exclude' => 0,
),
'full' =>
array (
'format' => 'default',
'exclude' => 0,
),
4 =>
array (
'format' => 'default',
'exclude' => 0,
),
2 =>
array (
'format' => 'default',
'exclude' => 0,
),
3 =>
array (
'format' => 'default',
'exclude' => 0,
),
'token' =>
array (
'format' => 'default',
'exclude' => 0,
),
),
),
);
$content['extra'] = array (
'title' => '-5',
'body_field' => '-1',
'revision_information' => '1',
'author' => '0',
'options' => '2',
'comment_settings' => '7',
'menu' => '6',
'taxonomy' => '-2',
'path' => '5',
'attachments' => '4',
'path_redirect' => '3',
);
$view = new view;
$view->name = 'bookmarks';
$view->description = 'Bookmarks added to the site';
$view->tag = '';
$view->view_php = '';
$view->base_table = 'node';
$view->is_cacheable = FALSE;
$view->api_version = 2;
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->override_option('fields', array(
'field_post_date_value' => array(
'label' => '',
'alter' => array(
'alter_text' => 0,
'text' => '<div class="bookmarkPostedDate">[field_post_date_value]</div>',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'link_to_node' => 0,
'label_type' => 'none',
'format' => 'default',
'multiple' => array(
'multiple_number' => '',
'multiple_from' => '',
'multiple_to' => '',
'group' => '',
),
'repeat' => array(
'show_repeat_rule' => '',
),
'fromto' => array(
'fromto' => 'both',
),
'exclude' => 0,
'id' => 'field_post_date_value',
'table' => 'node_data_field_post_date',
'field' => 'field_post_date_value',
'relationship' => 'none',
),
'title' => array(
'label' => 'Title',
'alter' => array(
'alter_text' => 0,
'text' => '',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'link_to_node' => 0,
'exclude' => 1,
'id' => 'title',
'table' => 'node',
'field' => 'title',
'relationship' => 'none',
),
'field_url_value' => array(
'label' => '',
'alter' => array(
'alter_text' => 1,
'text' => '[title]',
'make_link' => 1,
'path' => '[field_url_value] ',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'link_to_node' => 0,
'label_type' => 'none',
'format' => 'default',
'multiple' => array(
'group' => TRUE,
'multiple_number' => '',
'multiple_from' => '',
'multiple_reversed' => FALSE,
),
'exclude' => 0,
'id' => 'field_url_value',
'table' => 'node_data_field_url',
'field' => 'field_url_value',
'relationship' => 'none',
),
'body' => array(
'label' => '',
'alter' => array(
'alter_text' => 0,
'text' => '<div id="bookmarkDescription">[body]</div>',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 1,
'empty_zero' => 0,
'exclude' => 0,
'id' => 'body',
'table' => 'node_revisions',
'field' => 'body',
'relationship' => 'none',
),
'tid' => array(
'label' => 'in',
'alter' => array(
'alter_text' => 0,
'text' => '',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 1,
'empty_zero' => 0,
'type' => 'separator',
'separator' => ', ',
'link_to_taxonomy' => 1,
'limit' => 1,
'vids' => array(
'4' => 4,
'2' => 0,
'1' => 0,
'3' => 0,
),
'exclude' => 0,
'id' => 'tid',
'table' => 'term_node',
'field' => 'tid',
'relationship' => 'none',
),
));
$handler->override_option('sorts', array(
'field_post_date_value' => array(
'order' => 'DESC',
'delta' => -1,
'id' => 'field_post_date_value',
'table' => 'node_data_field_post_date',
'field' => 'field_post_date_value',
'relationship' => 'none',
),
));
$handler->override_option('filters', array(
'type' => array(
'operator' => 'in',
'value' => array(
'bookmarks' => 'bookmarks',
),
'group' => '0',
'exposed' => FALSE,
'expose' => array(
'operator' => FALSE,
'label' => '',
),
'id' => 'type',
'table' => 'node',
'field' => 'type',
'relationship' => 'none',
),
));
$handler->override_option('access', array(
'type' => 'none',
));
$handler->override_option('cache', array(
'type' => 'time',
'results_lifespan' => 3600,
'output_lifespan' => 3600,
));
$handler->override_option('title', 'Bookmarks');
$handler->override_option('use_pager', '1');
$handler->override_option('use_more', 0);
$handler->override_option('use_more_always', 0);
$handler->override_option('style_plugin', 'semanticviews_default');
$handler->override_option('style_options', array(
'grouping' => '',
'group' => array(
'element_type' => 'h3',
'class' => 'title',
),
'list' => array(
'element_type' => '',
'class' => '',
),
'row' => array(
'element_type' => 'div',
'class' => 'bookmarkItem',
'last_every_nth' => '0',
'first_class' => 'first',
'last_class' => 'last',
'striping_classes' => 'odd even',
),
));
$handler->override_option('row_plugin', 'semanticviews_fields');
$handler->override_option('row_options', array(
'semantic_html' => array(
'field_post_date_value' => array(
'element_type' => 'div',
'class' => 'bookmarksPostDate',
),
'field_url_value' => array(
'element_type' => 'div',
'class' => 'bookmarksURL',
),
'body' => array(
'element_type' => 'div',
'class' => 'bookmarksDescription',
),
'tid' => array(
'element_type' => 'div',
'class' => 'bookmarksTags',
'label_element_type' => 'label',
'label_class' => '',
),
),
'skip_blank' => 0,
));
$handler = $view->new_display('page', 'Bookmarks page', 'page_1');
$handler->override_option('path', 'bookmarks');
$handler->override_option('menu', array(
'type' => 'none',
'title' => '',
'description' => '',
'weight' => 0,
'name' => 'navigation',
));
$handler->override_option('tab_options', array(
'type' => 'none',
'title' => '',
'description' => '',
'weight' => 0,
'name' => 'navigation',
));
$view->name = 'bookmarks';
$view->description = 'Bookmarks added to the site';
$view->tag = '';
$view->view_php = '';
$view->base_table = 'node';
$view->is_cacheable = FALSE;
$view->api_version = 2;
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->override_option('fields', array(
'field_post_date_value' => array(
'label' => '',
'alter' => array(
'alter_text' => 0,
'text' => '<div class="bookmarkPostedDate">[field_post_date_value]</div>',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'link_to_node' => 0,
'label_type' => 'none',
'format' => 'default',
'multiple' => array(
'multiple_number' => '',
'multiple_from' => '',
'multiple_to' => '',
'group' => '',
),
'repeat' => array(
'show_repeat_rule' => '',
),
'fromto' => array(
'fromto' => 'both',
),
'exclude' => 0,
'id' => 'field_post_date_value',
'table' => 'node_data_field_post_date',
'field' => 'field_post_date_value',
'relationship' => 'none',
),
'title' => array(
'label' => 'Title',
'alter' => array(
'alter_text' => 0,
'text' => '',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'link_to_node' => 0,
'exclude' => 1,
'id' => 'title',
'table' => 'node',
'field' => 'title',
'relationship' => 'none',
),
'field_url_value' => array(
'label' => '',
'alter' => array(
'alter_text' => 1,
'text' => '[title]',
'make_link' => 1,
'path' => '[field_url_value] ',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'link_to_node' => 0,
'label_type' => 'none',
'format' => 'default',
'multiple' => array(
'group' => TRUE,
'multiple_number' => '',
'multiple_from' => '',
'multiple_reversed' => FALSE,
),
'exclude' => 0,
'id' => 'field_url_value',
'table' => 'node_data_field_url',
'field' => 'field_url_value',
'relationship' => 'none',
),
'body' => array(
'label' => '',
'alter' => array(
'alter_text' => 0,
'text' => '<div id="bookmarkDescription">[body]</div>',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 1,
'empty_zero' => 0,
'exclude' => 0,
'id' => 'body',
'table' => 'node_revisions',
'field' => 'body',
'relationship' => 'none',
),
'tid' => array(
'label' => 'in',
'alter' => array(
'alter_text' => 0,
'text' => '',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 1,
'empty_zero' => 0,
'type' => 'separator',
'separator' => ', ',
'link_to_taxonomy' => 1,
'limit' => 1,
'vids' => array(
'4' => 4,
'2' => 0,
'1' => 0,
'3' => 0,
),
'exclude' => 0,
'id' => 'tid',
'table' => 'term_node',
'field' => 'tid',
'relationship' => 'none',
),
));
$handler->override_option('sorts', array(
'field_post_date_value' => array(
'order' => 'DESC',
'delta' => -1,
'id' => 'field_post_date_value',
'table' => 'node_data_field_post_date',
'field' => 'field_post_date_value',
'relationship' => 'none',
),
));
$handler->override_option('filters', array(
'type' => array(
'operator' => 'in',
'value' => array(
'bookmarks' => 'bookmarks',
),
'group' => '0',
'exposed' => FALSE,
'expose' => array(
'operator' => FALSE,
'label' => '',
),
'id' => 'type',
'table' => 'node',
'field' => 'type',
'relationship' => 'none',
),
));
$handler->override_option('access', array(
'type' => 'none',
));
$handler->override_option('cache', array(
'type' => 'time',
'results_lifespan' => 3600,
'output_lifespan' => 3600,
));
$handler->override_option('title', 'Bookmarks');
$handler->override_option('use_pager', '1');
$handler->override_option('use_more', 0);
$handler->override_option('use_more_always', 0);
$handler->override_option('style_plugin', 'semanticviews_default');
$handler->override_option('style_options', array(
'grouping' => '',
'group' => array(
'element_type' => 'h3',
'class' => 'title',
),
'list' => array(
'element_type' => '',
'class' => '',
),
'row' => array(
'element_type' => 'div',
'class' => 'bookmarkItem',
'last_every_nth' => '0',
'first_class' => 'first',
'last_class' => 'last',
'striping_classes' => 'odd even',
),
));
$handler->override_option('row_plugin', 'semanticviews_fields');
$handler->override_option('row_options', array(
'semantic_html' => array(
'field_post_date_value' => array(
'element_type' => 'div',
'class' => 'bookmarksPostDate',
),
'field_url_value' => array(
'element_type' => 'div',
'class' => 'bookmarksURL',
),
'body' => array(
'element_type' => 'div',
'class' => 'bookmarksDescription',
),
'tid' => array(
'element_type' => 'div',
'class' => 'bookmarksTags',
'label_element_type' => 'label',
'label_class' => '',
),
),
'skip_blank' => 0,
));
$handler = $view->new_display('page', 'Bookmarks page', 'page_1');
$handler->override_option('path', 'bookmarks');
$handler->override_option('menu', array(
'type' => 'none',
'title' => '',
'description' => '',
'weight' => 0,
'name' => 'navigation',
));
$handler->override_option('tab_options', array(
'type' => 'none',
'title' => '',
'description' => '',
'weight' => 0,
'name' => 'navigation',
));
#!/usr/bin/env python
import optparse, sys, time, xmlrpclib
from lxml import etree
def setupConfig(url, username, password):
return {
'url': url,
'username': username,
'password': password
}
def parseDeliciousBookmarks(filename):
bookmarksTree = etree.parse(filename)
postTree = bookmarksTree.xpath("//post")
count = 1
total = len(postTree)
for post in postTree:
href = post.get("href")
tags = post.get("tag")
description = post.get("description")
extended = post.get("extended")
postedDatetime = post.get("time")
tagsList = tags.split(" ")
# Fill out the node values that we can
# create our taxonomy terms
terms = [tag for tag in tagsList]
postedDatetime = postedDatetime[0:len(postedDatetime) - 1]
tuple_time = time.strptime(postedDatetime, "%Y-%m-%dT%H:%M:%S")
postedDate = time.strftime("%d %B %Y", tuple_time)
postedTime = time.strftime("%I:%M%p", tuple_time)
node = {
'type': 'bookmarks',
'status': 1,
'title': description,
'body': extended,
'field_url': [
{'value': href}
],
'field_post_date': [
{
'value': {
'date': postedDate,
'time': postedTime,
},
'timezone': 'UTC',
'date_type': 'date'
}
],
'tags': terms
}
print "%d of %d: %s (%s) - %s - %s (Posted on %s at %s)" % (count, total, description, href, extended, tags, postedDate, postedTime)
count += 1
yield node
def updateTaxonomyMapping(vocabulary, server, sessid):
taxonomyTree = server.taxonomy.getTree(sessid, vocabulary)
# Create a simple mapping from term to termID
taxonomyMapping = {}
for tag in taxonomyTree:
taxonomyMapping[tag["name"]] = tag["tid"]
return taxonomyMapping
if __name__ == "__main__":
parser = optparse.OptionParser()
parser.add_option("-s", "--server", action = "store", type="string", dest = "server", help = "Server to connect to, without 'services/xmlrpc'")
parser.add_option("-u", "--username", action = "store", type="string", dest = "username", help = "Username, with necessary services rights")
parser.add_option("-p", "--password", action = "store", type="string", dest = "password", help = "Password")
parser.add_option("-v", "--vocabulary", action = "store", type="int", dest = "vocabulary", help = "Vocabulary id to store tags in. Defaults to 1", default = 1)
parser.add_option("-f", "--file", action = "store", type="string", dest = "file", help = "Path to XML file containing bookmarks exported from delicious; defaults to 'bookmarks.xml'", default = "bookmarks.xml")
(options, args) = parser.parse_args()
if options.server is None:
parser.error("Server is required")
elif options.username is None:
parser.error("Username is required")
elif options.password is None:
parser.error("Password is required")
config = {
'url': options.server + "/services/xmlrpc",
'username': options.username,
'password': options.password
}
server = xmlrpclib.ServerProxy(config['url'], allow_none = True)
connection = server.system.connect()
session = server.user.login(connection["sessid"], config['username'], config['password'])
sessid = session['sessid']
user = session['user']
vocabulary = options.vocabulary
taxonomyMapping = updateTaxonomyMapping(vocabulary, server, sessid)
for bookmark in parseDeliciousBookmarks(options.file):
bookmark['uid'] = user['uid']
bookmark['name'] = user['name']
bookmark['created'] = str(int(time.time()))
# Check our taxonomy terms
terms = bookmark["tags"]
for term in terms:
if not term in taxonomyMapping.keys():
newTerm = {"vid": vocabulary, "name": term}
result = server.taxonomy.saveTerm(sessid, newTerm)
taxonomyMapping = updateTaxonomyMapping(vocabulary, server, sessid)
termIDs = [taxonomyMapping[term] for term in terms]
del bookmark["tags"]
bookmark["taxonomy"] = termIDs
try:
n = server.node.save(sessid, bookmark)
print "Created nodeID ", n
except xmlrpclib.Fault, err:
print "A fault occurred"
print "Fault code: %d" % err.faultCode
print "Fault string: %s" % err.faultString
import optparse, sys, time, xmlrpclib
from lxml import etree
def setupConfig(url, username, password):
return {
'url': url,
'username': username,
'password': password
}
def parseDeliciousBookmarks(filename):
bookmarksTree = etree.parse(filename)
postTree = bookmarksTree.xpath("//post")
count = 1
total = len(postTree)
for post in postTree:
href = post.get("href")
tags = post.get("tag")
description = post.get("description")
extended = post.get("extended")
postedDatetime = post.get("time")
tagsList = tags.split(" ")
# Fill out the node values that we can
# create our taxonomy terms
terms = [tag for tag in tagsList]
postedDatetime = postedDatetime[0:len(postedDatetime) - 1]
tuple_time = time.strptime(postedDatetime, "%Y-%m-%dT%H:%M:%S")
postedDate = time.strftime("%d %B %Y", tuple_time)
postedTime = time.strftime("%I:%M%p", tuple_time)
node = {
'type': 'bookmarks',
'status': 1,
'title': description,
'body': extended,
'field_url': [
{'value': href}
],
'field_post_date': [
{
'value': {
'date': postedDate,
'time': postedTime,
},
'timezone': 'UTC',
'date_type': 'date'
}
],
'tags': terms
}
print "%d of %d: %s (%s) - %s - %s (Posted on %s at %s)" % (count, total, description, href, extended, tags, postedDate, postedTime)
count += 1
yield node
def updateTaxonomyMapping(vocabulary, server, sessid):
taxonomyTree = server.taxonomy.getTree(sessid, vocabulary)
# Create a simple mapping from term to termID
taxonomyMapping = {}
for tag in taxonomyTree:
taxonomyMapping[tag["name"]] = tag["tid"]
return taxonomyMapping
if __name__ == "__main__":
parser = optparse.OptionParser()
parser.add_option("-s", "--server", action = "store", type="string", dest = "server", help = "Server to connect to, without 'services/xmlrpc'")
parser.add_option("-u", "--username", action = "store", type="string", dest = "username", help = "Username, with necessary services rights")
parser.add_option("-p", "--password", action = "store", type="string", dest = "password", help = "Password")
parser.add_option("-v", "--vocabulary", action = "store", type="int", dest = "vocabulary", help = "Vocabulary id to store tags in. Defaults to 1", default = 1)
parser.add_option("-f", "--file", action = "store", type="string", dest = "file", help = "Path to XML file containing bookmarks exported from delicious; defaults to 'bookmarks.xml'", default = "bookmarks.xml")
(options, args) = parser.parse_args()
if options.server is None:
parser.error("Server is required")
elif options.username is None:
parser.error("Username is required")
elif options.password is None:
parser.error("Password is required")
config = {
'url': options.server + "/services/xmlrpc",
'username': options.username,
'password': options.password
}
server = xmlrpclib.ServerProxy(config['url'], allow_none = True)
connection = server.system.connect()
session = server.user.login(connection["sessid"], config['username'], config['password'])
sessid = session['sessid']
user = session['user']
vocabulary = options.vocabulary
taxonomyMapping = updateTaxonomyMapping(vocabulary, server, sessid)
for bookmark in parseDeliciousBookmarks(options.file):
bookmark['uid'] = user['uid']
bookmark['name'] = user['name']
bookmark['created'] = str(int(time.time()))
# Check our taxonomy terms
terms = bookmark["tags"]
for term in terms:
if not term in taxonomyMapping.keys():
newTerm = {"vid": vocabulary, "name": term}
result = server.taxonomy.saveTerm(sessid, newTerm)
taxonomyMapping = updateTaxonomyMapping(vocabulary, server, sessid)
termIDs = [taxonomyMapping[term] for term in terms]
del bookmark["tags"]
bookmark["taxonomy"] = termIDs
try:
n = server.node.save(sessid, bookmark)
print "Created nodeID ", n
except xmlrpclib.Fault, err:
print "A fault occurred"
print "Fault code: %d" % err.faultCode
print "Fault string: %s" % err.faultString
Comments
The Creative Li... replied on Permalink
Pingback
links for 2011-... replied on Permalink
Pingback
Vacilando replied on Permalink
Drupal 7 version?
Adriana Knouf replied on Permalink
Not at the moment
Add new comment