Friday, March 16, 2018

GCP CloudEndure: Failed to create a Replication Server

If you ever encounter this and have no clue what the problem is even after visiting the help page, then it might be the Default subnet in the settings page in CloudEndure. Use default (default) instead:


Wednesday, December 13, 2017

Odoo XMLRPC: create ir.attachment with non-ascii datas

Consider the following Python 3 code:

import xmlrpc.client
import functools
import base64

url = 'http://myodoo:8069'
db = 'a_database'
username = 'admin'
password = 'admin'

common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url))
uid = common.authenticate(db, username, password, {})

models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url))
call = functools.partial(models.execute_kw, db, uid, password)

vals = {'res_model': 'crm.lead', 'company_id': 1, 'res_name': 'A Name', 
        'res_id': 1, 'datas_fname': 'TEST.txt', 'name': 'TEST.txt'}
  
vals.update({
  'datas': 'Hi!'
})
call('ir.attachment', 'create', [vals])


This should work fine. However, if the data contains non-ASCII characters, the server side complains:

ValueError: string argument should contain only ASCII characters


So we must encode the data, but it must stay ASCII. To do that, use this:

'datas': base64.b64encode("allĂ´!".encode()).decode('ascii')


Thursday, June 1, 2017

Odoo restore DB: No space left on device

If you encounter this error while restoring a database using Odoo's web database manager, it might be because the device where your temporary folder is, is out of free space. In my case, it was /tmp. So I couldn't just free up space, so I wanted to change the temporary folder Odoo uses. Turns out Odoo uses the Python module tempfiles and it can use environment variables.

So, to set a different temporary folder, set the TMPDIR environment variable, run Odoo, and then proceed to the database restore.

$ export TMPDIR=/home/jerther
$ ./odoo-bin

Wednesday, March 22, 2017

Odoo 8 Many2many or One2many users field domain based on group

Here's an example of a collection of users that I wanted to be filtered by some group:

def _get_users_domain(self):
    return [('groups_id', 'in', self.env.ref('topo.users').id)]

user_ids = fields.Many2many('res.users', domain=_get_users_domain, string="Users", help="Who is allowed to access this board")

When you expose the field in a view, the selection widget should only show users that are members of the given group.

This should also work for a One2many field.

Thursday, January 26, 2017

UnitTest odoo 8 modules with coverage

At the time of this writing, help is a bit sparse on testing with odoo, let alone testing inside eclipse. Although I've been able to achieve it, it's quite the journey so I've decided to write this article.

Before we start, if you've not done so already, take a look at Python UnitTest Framework doc and then Testing Odoo Modules . I now assume you know how to write and organize unit tests for odoo. Also, in the latter link, you'll read that testing outside odoo is not supported. Which might be true for the odoo team, but lately I came accross a really neat plugin. But enough of this, let's dig in!

We're gonna be using pytest and the plugin won't work with older versions so make sure it is up to date:
sudo pip install -U pytest
Then install pytest-odoo plugin:
sudo pip install pytest-odoo
Then you need to export a variable that points to an odoo config file with some database information:
export OPENERP_SERVER=/etc/odoo/openerp-server.conf
If you're already running odoo, this file should already be configured but could miss this important parameter so make sure it is there:
db_name = testjerther
And last, go to the root of your module and run:
pytest -v
Bingo!


I'd like to run this inside Eclipse with its pytest runner, but I gave up after (too) many hours.

Now for coverage, install pytest-cov
sudo pip install pytest-cov
You can then add coverage to your tests with:
pytest -v --cov
But that gives a LOT, and I found that using --cov=moduleName does not work, but we can apply a filter in a config file. So go ahead and create one in the root folder of your project and put these lines in:
[run]
include = */moduleName/*
There are many other parameters that can go in there. Now you can run:
pytest -v --cov --cov-config=coverage.config

You can also export the coverage in html by adding --cov-report html:cov_html where cov_html is a folder. check out the doc for more options!

Tuesday, April 19, 2016

TFS 2015: Build failed but no issues and WI images not showing

Some people had this problem and it was due to some permissions but it got fixed with Update 1. I still had it with the recently released Update 2. The build would fail with no issue, no log, no nothing.

Also, attached images in work items would not show!

The problem was that we upgraded from TFS 2010, and the upgrade did not update all the Application Tier URLs: the Notification URL was still set to an old URL. Correcting this fixed the problem!


Wednesday, September 23, 2015

Visual Paradigm: Draw a table

Just a quick tip. Here's how to create a table in Visual Paradigm.

First, create a Text box, then use this associated tool:

Result:
This may seem quite obvious but at first I had to look around for quite a bit. Hope this help save some time.

Thursday, August 13, 2015

Vino VNC asks for keyring password

When you try to connect with VNC to your Ubuntu desktop which has Vino configured and running, it may trigger a gnome keyring password prompt on the local display, and the login process hangs on the remote end. There are many solutions around, and most of them leave the system somewhat dirty. Here's a procedure I've gathered which fixes the problem, even remotely, and leave the system clean.

First of all, we need to shut down the gnome keyring daemon. But I've found that all application trying to gain access to the keyring will cause it to launch. So I found the only way to stop it without removing it is to rename it. Temporarily, don't worry:

sudo mv /usr/bin/gnome-keyring-daemon /usr/bin/gnome-keyring-daemon.bak
sudo reboot

Then you should be able to connect to the remote machine with VNC. But let's not stop here and do things proper. First, reenable gnome keyring daemon and launch it so it is available to the next step:

sudo mv /usr/bin/gnome-keyring-daemon.bak /usr/bin/gnome-keyring-daemon
gnome-keyring-daemon

Make sure seahorse is installed and launch it from the VNC session. It's in System / Preferences / Passwords and Keys.

Now there's this "Login" folder. Unlock it by right clicking and choosing Unlock.

In the login folder, you will find an item named Vino. Right click on it and delete it.

And that's it. You can make sure everything is fixed by rebooting and trying to connect with VNC again.

I believe you have to leave the VNC password blank. Otherwise it'll create this item in the keyring. Feel free to confirm this in the comments. I've pulled too much hair out for this already ;)

Thursday, February 5, 2015

WebAPI OData v4: Cannot serialize a null 'entry'

In that kind of function:

public IHttpActionResult MyFunction([FromODataUri]int myParameter)
{
 return Ok(GetFoo(myParameter));
}

This may lead to the error 500 described in the title if the returned value is null. Based on this thread, One way would be to return an empty response when the return value is null:

public IHttpActionResult MyFunction([FromODataUri]int myParameter)
{
 var retval = GetFoo(myParameter);
 if (retval != null)
  return Ok(retval);
 else
  return Ok();
}

The result is a 202 OK empty response, but this doesn't seem to please some OData frameworks as I sometimes get a "Unexpected server response" message on the client side.

Now, based on this other thread, it seems the clean way to do this is to return a 404 error when the return value is null and act accordingly on the client side:

public IHttpActionResult MyFunction([FromODataUri]int myParameter)
{
 var retval = GetFoo(myParameter);
 if (retval != null)
  return Ok(retval);
 else
  return NotFound();
}


Wednesday, January 14, 2015

WebAPI OData: Only entity types support $select and $expand

I used to get this because I was trying to return a complex type which was not an entity type and use "expand" on it so to get a child property:

builder.EntitySet<MyComplexType>("MyComplexTypes").EntityType.Collection.Action("MyAction").Returns<MyComplexType>()
[HttpPost, EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Expand)]
public IHttpActionResult MyAction(ODataActionParameters parameters)
{
 return Ok(new MyComplexType());
}

http://host/api/Entities/Default.MyAction?$expand=ChildProperty

Accessing this URL triggers:

"Only entity types support $select and $expand"

Instead of converting the type, which would have made non sens in my situation, here's what I did:

builder.EntitySet<MyComplexType>("MyComplexTypes").EntityType.Collection.Action("MyAction").Returns<string>()
[HttpPost]
public IHttpActionResult MyAction(ODataActionParameters parameters)
{
 return Json(new MyComplexType());
}

http://host/api/Entities/Default.MyAction

This returns a new MyComplexType object with all its children properties serialized.

Saturday, December 13, 2014

Play C64 SID files under Linux

VLC didn't work, neither did totem, and couldn't get sidplay to work.

Install audacity
then install the "exotic" plugins package, namely audacious-plugins-exotic

There you go. You can load and play your sid files from within audacity.


Thursday, December 4, 2014

ASP.Net OData V4 with inherited key: A primitive value was specified; however, a value of the non-primitive type '' was expected

At the time of writing this, there is absolutely no help on the net on this error I keep on getting. By trial and error, I've been able not to explain the message, but at least find the cause.

In my case, I have two classes:

class BaseClass
{
  public int ID {get; set;}
}
class DerivedClass : BaseClass {  public string A { get; set; }
}

And in the modelBuilder configuration (which may not be necessary here but in my case, the builder would treat some other property as the primary key instead of this one and caused other cryptic errors):

builder.EntitySet<DerivedClass>("DerivedClasses").EntityType.HasKey(c => c.ID);

Now in my DerivesClassesController ODataController, if I try to access the derived class like so:

http://someaddress/api/DerivedClasses

I get this OData V4 formatted error (in french) :
{ "error":{ "code":"","message":"An error has occurred.","innererror":{ "message":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata.metadata=minimal'.","type":"System.InvalidOperationException","stacktrace":"","internalexception":{ "message":"Une valeur primitive a \u00e9t\u00e9 sp\u00e9cifi\u00e9e alors qu'une valeur de type non primitif '' \u00e9tait attendue.","type":"Microsoft.OData.Core.ODataException","stacktrace":" \u00e0 Microsoft.OData.Core.ValidationUtils.ValidateIsExpectedPrimitiveType(Object value, IEdmPrimitiveTypeReference valuePrimitiveTypeReference, IEdmTypeReference expectedTypeReference, Boolean bypassValidation)\r\n \u00e0 .............................
(Here I used to suggest defining a "new int ID" in the DerivedClass which worked but was kind of dirty and defeated the whole base class purpose. Here's a better way:)

The trick is to map the base class' ID property:

builder.EntityType<BaseClass>().HasKey(c => c.ID);

And do not remap the key on each derived class or you'll end up with a cryptic "The sequence contains more than one element" error.

The problem with this, however, is that the builder will try to map every entity that is inherited from the base class which you might or might not want. As of this writing, there is no way of preventing this automatic discovery.

So another trick is to explicitly ignore all derived types and only after that, remap wanted entities. Doing this by hand would be problematic wouldn't it. So here's a nice reflective loop that'll do the ignore part in a couple of lines. Place this just after the base class key definition so you end up with:

builder.EntitySet<DerivedClass>("DerivedClasses").EntityType.HasKey(c => c.ID);

var types = AppDomain.CurrentDomain.GetAssemblies()
 .SelectMany(a => a.GetTypes())
 .Where(t => t.IsSubclassOf(typeof(AbstractModel)));

foreach (var type in types)
 builder.Ignore(types.ToArray());

Then proceed on the derived types mappings as usual.

Wednesday, December 3, 2014

ASP.Net: OData V4 Controller giving 406

What an obscure one...

If you have a seemingly complete controller and opening this url:

http://yoursite/api/Products

returns actual OData v4 results BUT this:

http://yoursite/api/Products(1)

returns a 404 error with:

No type was found that matches the controller named 'Products(1)'

then make sure your "using"s are adequate in your controller:

using System.Web.OData; <-- this should be used

vs

using System.Web.Http.OData;

By default, the WebAPI 2 ODataController template generates a class with the latter, and it gave me trouble.

Friday, November 28, 2014

ASP.Net: Web API 2 + Help Pages + OData

Doing each of them separately is fairly easy and there's lots of tutorials on how to do it. But as for mixing them together, I've not seen much. Some blogs claim it's not possible and buggy and mostly lead to 404 and 406 errors. Well, out of luck, I made it work. The secret is in the ROUTING!

I don't understand all the concepts behind this and I believe most of us don't, and don't care to. Still, feel free to explain the unexplained in the comments.

So, I started with a ASP.Net Application project. I Selected Web API for the template so I could have the help pages ready. Then I added the needed stuff for OData V4 following this really nice tutorial. Last, I had to configure the ROUTES, and that is where all hell resides (well, that's what all the blog posts and obscure SO questions lead me to think). THe following has been taken from my SO answer.

The order in which the routes are configured has an impact. In my case, I also have some standard MVC controllers and help pages.

Global.asax:
protected void Application_Start()
{
 AreaRegistration.RegisterAllAreas();
 GlobalConfiguration.Configure(config =>
 {
  ODataConfig.Register(config); //this has to be before WebApi
  WebApiConfig.Register(config);
 });
 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
 RouteConfig.RegisterRoutes(RouteTable.Routes);
}

The filter and routeTable parts weren't there when I started my project and are needed.

ODataConfig.cs:
public static void Register(HttpConfiguration config)
{
  config.MapHttpAttributeRoutes(); //This has to be called before the following OData mapping, so also before WebApi mapping
    
  ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
    
  builder.EntitySet("Sites");
  //Moar!
    
  config.MapODataServiceRoute("ODataRoute", "api", builder.GetEdmModel());
}

WebApiConfig.cs:
public static void Register(HttpConfiguration config)
{
  config.Routes.MapHttpRoute( //MapHTTPRoute for controllers inheriting ApiController
     name: "DefaultApi",
     routeTemplate: "api/{controller}/{id}",
     defaults: new { id = RouteParameter.Optional }
  );
}

RouteConfig.cs:
public static void RegisterRoutes(RouteCollection routes)
{
  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
  routes.MapRoute( //MapRoute for controllers inheriting from standard Controller
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
  );
}

This has to be in that EXACT ORDER. I tried moving the calls around and ended up with either MVC, Api or Odata broken with 404 or 406 errors.

So I can call:

localhost:xxx/ -> leads to help pages (home controller, index page)

localhost:xxx/api/ -> leads to the OData $metadata

localhost:xxx/api/Sites -> leads to the Get method of my SitesController inheriting from ODataController

localhost:xxx/api/Test -> leads to the Get method of my TestController inheriting from ApiController.

Saturday, November 22, 2014

Linux: Slow wifi with Intel and iwl3945

I have this lovely Thinkpad T60 on which I replaced the Atheros Wifi card for an Intel one a few years ago. I'm using Fedora 20 on it and noticed the wifi connection, although negociated to 54Mbps wouldn't get any better actual speed than 1 Mbps. I was on the edge of getting an Atheros back but here's what I did to fix the problem:

As root, add a new file:
/etc/modprobe.d/iwl3945.conf
With content:
options iwl3945 disable_hw_scan=0
then:
modprobe -r iwl3945 
modprobe iwl3945
No need to restart, the changes were instantaneous.

I don't know if it also helped, but prior to that I had removed NetworkManager and installed wicd instead. Look around on how to do this if the above solution alone didn't work. One thing though if you install wicd-gtk, I had to add my user to the "users" group and then reboot. This had nothing to do with the problem. I reinstalled NetworkManager and everything was still fine.

All thanks to this comment on ubuntu launchpad. It's from 2011 but still works.

Monday, November 17, 2014

WatchGuard: failed to open shared memory for openvpn command

I was having problem connecting to a VPN using WatchGuard Mobile VPN with SSL. Here's my log:
2014-11-17T10:16:27.568 VERSION file is 5.23, client version is 5.23
2014-11-17T10:16:27.851 failed to open shared memory for openvpn command (error: 2), please check the WatchGuard SSLVPN Service
2014-11-17T10:16:27.851 Failed to launched openvpn. retCP=0
I tried various things, and it turns out that I was using version 11.9.1. I uninstalled the client and reinstalled the version located on the firewall, which was 11.7.3 and now everything works.

Along the hair pulling and googling, the typo in the last log line made me laugh a bit.

Friday, October 31, 2014

jQuery default JSON parser for AJAX calls

By default, jQuery auto detects the response format of an AJAX request and uses a predefined list of converters to use. Here's how I set the default converter when an AJAX call returns JSON data:
$.ajaxSetup({ converters: { '* text': window.String, 'text html': true, 'text json': parseDotNetJson, 'text xml': jQuery.parseXML } }); function parseDotNetJson(data) { return JSON.parse(data, function(key, value) { if (typeof value === 'string') { var a = /\/Date\((-?\d*)\)\//.exec(value); if (a) return new Date(+a[1]); } return value; }); }
I place this code in the root of a script file. I use this to convert dates parsed to JSON by ASP.Net MVC.

More info

Thursday, October 30, 2014

DevExtreme - Get dxTextBox value the JQuery way

Fairly simple. Let's say you have this:
<div id="batch-id"></div>
Which is converted to a dxTextBox:
$('#batch-id').dxTextBox();
And you want to get the value the official way:
var x = $('#batch-id').dxTextBox('instance').option('value');
You can set the value this way:
$('#batch-id').dxTextBox('instance').option('value', 666);
You can also directly query the element using classic jQuery:
$('#batch-id input').val();

Saturday, October 25, 2014

CoCo 2 with a PC 3.5 floppy drive

Now for something a little bit more vintage. I've revived my TRS-80 Color Computer 2 and wanted to give it an ultimate go with all the stuff that's now available on the net. The CoCo community is not very large, but it's still there. So I went on and tried to get the poor old thing to communicate with the now popular DriveWire 4 through a serial cable. But that's another blog post.

I'll talk here about the 3.5 inch floppy drive. They're still kind of available and cheap so, they're good replacements for the original 5.25 drives, and more reliable too. I looked around for many hours and found much complicated stuff, but I just wanted to sink a couple of boats in Battle Stations. So I decided to write this post, should it be helpful to someone one day, who also decides to blow some dust off their CoCo 2 ;)

Hooking the drive to a CoCo

Alright so here's what you'll need, no modification, no hack, no fiddling with the circuits:
  • A CoCo 2 system with a floppy disk controller.
  • A floppy cable, straight trough, with the two kinds of connectors (one "edge connector" for the controller, and a "plug connector" for the drive) Those used to be common. You could also use a edge to plug converter and use a more nowadays-common "all-plugs" cable.
  • A 3.5 floppy drive
  • A power source to power the drive.
This part is fairly easy. You just hook the drive to the controller. And that's it. I used the two connectors that were the closest to each other. Forgive the arrangement, it's messy, but it works:

I first used a nearby PC (they're everywhere in my appartment!) to power the drive.
Start the CoCo, type in DRIVE 1, and you're all set. You can insert a floppy, DSKINI1 on it, LOAD" from and SAVE" to it. You might have to put a piece of tape on the HD hole of the floppy to make it low density.

What if I need to use Drive 0?

For technical reasons, all standard PC floppy drives are hard wired to be DRIVE 1. Some have jumpers that make this configurable:
See DS1 and DS0? DS1 is jumped. You would have to unjump DS1 and jump DS0.
But that's not all. The CoCo uses a different technique than on PC to select which drive it talks to so the ordinary twisted cable won't work:
"There are 34 wires in the ribbon cable (1-34). Each odd wire is Ground. Each even wire is a control or data line. The Drive Select 0 and Drive Select 1 wires (numbers 12 and 14) are separated by 1 ground wire (13). 
You have to carefully remove the ribbon cable from the connector, seperate those three wires (DS0, GND, DS1) from the others and twist them so that the two drive select wires switch positions, then put all 34 wires back into the connector." -- Source

Writing a CoCo DSK image to a floppy from a PC

Here's what you'll need:
  • A PC (The OS will define which software you'll use. I used Debian.)
  • Another floppy drive attached to the motherboard of the PC. USB drives can't work.
I'll only talk about how to do it using Debian. Other Linux distros should have no real problem adapting the instructons. As for Windows, you're on your own, sorry... You might just want to get yourself a nice LiveBoot distribution of Ubuntu. Go ahead, the Linux turn nowadays is a fun and friendly journey! ;)

Ensure you have your floppy drive hooked to your motherboard. Then, you'll need fdutils so in a terminal:
sudo apt-get install fdutils
As a side note, no additional configuration of the utils is needed. apt will also install mtools as a dependency which will come with the proper /etc/mediaprm file configured with the correct CoCo entries in it.

Next:
  • Put a piece of tape on the HD hole of the floppy to make it low density ;)
  • Run the following command to tell the system how to format the disk. You'll have to run this every time you put a floppy in the drive:
sudo setfdprm /dev/fd0 coco1
  • Format the disk:
sudo fdformat /dev/fd0
  • Write the image to the disk:
sudo dd if=bstations.dsk of=/dev/fd0 
  • If any of the above two commands fail to read data from the disk, you might try to reenter the fdformat command and try again. this happened to me a couple of times.
  • Put the disk in the CoCo drive and sink some boats ;)

Reading a CoCo floppy to a DSK image on a PC

Install fdutils as explained above. Next, issue the setfdprm command explained above and enter:
sudo dd if=/dev/fd0 of=myimage.dsk count=315
If the dd command fails to write to the disk, you might have to enter the setfdprm command a couple of times before the dd command works, at least each time a floppy is inserted.

Floppy power source

If you want to build a power source yourself, you only need to provide 5V. Most floppies will need around 1A. No need for 12V. Be aware that the cartridge port of the CoCo has a 5V pin, but it is rated for 300mA only, so be safe and use an external source.
I used a spare D-Link wall adapter and replaced its connector with a floppy power connector I had left.

Hook both a 3.5 floppy drive and a CoCo drive to the CoCo

This is fairly easy. You'll need to ensure your floppy cable has enough connectors, and that there is no twist in between the used connectors.

Next, proceede to a "dental extraction" on the connector that'll connect the CoCo drive. Look at your original CoCo drive cable, if you have one, and look at the DRIVE 0 connector. You'll notice some pins (3) are missing. You'll have to remove them from your connector as well. So gently (those things are fragile like crystal) open the connector, remove the cable, remove the uneeded pins, put the cable back and reassemble the connector. Here's mine:
Notice the three removed pins. This is for DRIVE 0.
This is the whole setup.
There you have it. You can now backup your old 5.25 floppies to more trustworthy 3.5's!
You'll need one 3.5 for each 5.25 side, but who cares ;)

What if my 3.5 drive is DRIVE 0?

Then you want the CoCo as DRIVE 1. The process is the same, but the removed pins will be different:
This is for DRIVE 1 on the original CoCo cable. As a side note, also notice the straighten twist on the modified cable.

Additional infos

Here's a great repository of disk images I use myself. You'll find Battle Stations somewhere in there ;)

So this is what has worked for me. I tried a lot of things and failed many times, wondering each time what was wrong. Don't give up! I'd like to thank the guys at #coco_talk on freenode, they've been a great help and very friendly!

Feel free to ask any question in the comments below!

Wednesday, September 24, 2014

VS: Automatically create a NuGet Package from project upon build

Here's a neat little approach I use to effortlessly build NuGet packages.

- Get NuGet.exe and put it in the project folder. I also add it to our source control.
- Create a new Build Target named "NuGet". (optional, see below)
- In the Post Build Events of the project you want to make a NuGet package from, enter this:
if $(ConfigurationName) == NuGet (
  "$(ProjectDir)nuget.exe" pack "$(ProjectDir)$(ProjectFileName)" -OutputDirectory "\\target\directory\NuGet Feed" -Prop Configuration=$(ConfigurationName)
)
To use it, you just select the NuGet target, and build the project. It'll build the package and put it in the OutputDirectory folder.

The new "NuGet" target is in fact optional and you could use "Release" in the Post Build Event condition.

Also, you might want to edit the project's Assembly Infos and set the Company, Name, and Description properties. Pay special attention to the Version property too. Those properties appear in the NuGet package manager.

For more information on NuGet and how to get it, check this out.
To learn how to make a NuGet feed, check this out. Especially the local one. It's as simple as making a new directory, and this is what I use.

Have fun!

UPDATE:
Here's a NuGet with more features to do just that:
https://www.nuget.org/packages/CreateNewNuGetPackageFromProjectAfterEachBuild/