diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/EntityRetriever.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/EntityRetriever.java
index 916481ae4e87baeb62662592d6ab5c465554a254..e06ea3716ef0158b652cd2088a1e1a2018a31d9d 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/EntityRetriever.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/EntityRetriever.java
@@ -133,6 +133,7 @@ public class EntityRetriever implements IEntityRetriever
             Node<Project> prjNode = new Node<Project>(project);
             graph.addNode(prjNode);
             addExperiments(prjNode);
+            addSamplesForProject(prjNode);
         }
 
         // add space samples
@@ -164,8 +165,11 @@ public class EntityRetriever implements IEntityRetriever
 
     private void addSpaceSamples(String spaceCode)
     {
+        // Add samples that are connected with a space only (i.e. have project == null and experiment == null).
+
         SampleSearchCriteria criteria = new SampleSearchCriteria();
         criteria.withSpace().withCode().thatEquals(spaceCode);
+        criteria.withoutProject();
         criteria.withoutExperiment();
         criteria.withAndOperator();
 
@@ -195,6 +199,8 @@ public class EntityRetriever implements IEntityRetriever
     {
         for (Sample sample : expNode.getEntity().getSamples())
         {
+            // Add samples that are connected with an experiment and optionally a project.
+
             Node<Sample> sampleNode = new Node<Sample>(sample);
             graph.addEdge(expNode, sampleNode, new Edge(CONNECTION));
 
@@ -203,6 +209,23 @@ public class EntityRetriever implements IEntityRetriever
         }
     }
 
+    private void addSamplesForProject(Node<Project> prjNode)
+    {
+        for (Sample sample : prjNode.getEntity().getSamples())
+        {
+            // Add samples that are connected with a project only (i.e. have experiment == null).
+
+            if (sample.getExperiment() == null)
+            {
+                Node<Sample> sampleNode = new Node<Sample>(sample);
+                graph.addEdge(prjNode, sampleNode, new Edge(CONNECTION));
+
+                addDataSetsForSample(sampleNode);
+                addChildAndComponentSamples(sampleNode);
+            }
+        }
+    }
+
     private void addDataSetsForExperiment(Node<Experiment> expNode)
     {
         for (DataSet dataSet : expNode.getEntity().getDataSets())
@@ -339,6 +362,7 @@ public class EntityRetriever implements IEntityRetriever
         fo.withSpace();
         fo.withAttachments();
         fo.withExperimentsUsing(createExperimentFetchOptions());
+        fo.withSamplesUsing(createSampleFetchOptions());
         return fo;
     }
 
@@ -361,6 +385,7 @@ public class EntityRetriever implements IEntityRetriever
         fo.withDataSets();
         fo.withType();
         fo.withExperiment();
+        fo.withProject();
         fo.withSpace();
         fo.withAttachments();
         fo.withChildrenUsing(fo);
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/entitygraph/Node.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/entitygraph/Node.java
index 0468aba9952ee15d0961387a00cfa2b8d0d384e7..45e219ceb0198a89983009ad52074c0c79e1dfc6 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/entitygraph/Node.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/common/entitygraph/Node.java
@@ -142,7 +142,9 @@ public class Node<T extends IModificationDateHolder & IModifierHolder & IRegistr
         {
             return null;
         }
-        return ((IProjectHolder) entity).getProject().getCode();
+        
+        Project project = ((IProjectHolder) entity).getProject(); 
+        return project != null ? project.getCode() : null;
     }
 
     public Sample getSampleOrNull()
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataParser.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataParser.java
index 551fc823ebeb9dc906d4ae145d4ecafc2aa7457d..74cf84519b789a979573c04cd6c8047e5bb7533e 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataParser.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/MasterDataParser.java
@@ -190,7 +190,7 @@ public class MasterDataParser
             String entityKind = getAttribute(pluginElement, "entityKind").trim();
             plugin.setScriptType(ScriptType.valueOf(getAttribute(pluginElement, "type")));
             plugin.setPluginType(PluginType.JYTHON);
-            plugin.setEntityKind(entityKind.equals("") ? null : new EntityKind[] { EntityKind.valueOf(entityKind) });
+            plugin.setEntityKind((entityKind.equals("") || entityKind.equals("All")) ? null : new EntityKind[] { EntityKind.valueOf(entityKind) });
             plugin.setScript(pluginElement.getTextContent());
             validationPlugins.put(plugin.getName(), plugin);
         }
diff --git a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParser.java b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParser.java
index e1fa5a6b8882e5a8a0b949e4269c9ecc524c0a6e..c30c27c08ae014662223098b2f345b94e5f4e494 100644
--- a/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParser.java
+++ b/datastore_server/source/java/ch/ethz/sis/openbis/generic/server/dss/plugins/sync/harvester/synchronizer/ResourceListParser.java
@@ -183,8 +183,7 @@ public class ResourceListParser
             if (uri.endsWith("MASTER_DATA/MASTER_DATA/M"))
             {
                 parseMasterData(doc, xpath, uri);
-            }
-            else if (uri.endsWith("/M"))
+            } else if (uri.endsWith("/M"))
             {
                 list.add(uri);
             }
@@ -218,20 +217,16 @@ public class ResourceListParser
         if (SyncEntityKind.PROJECT.getLabel().equals(entityKind))
         {
             parseProjectMetaData(xpath, extractPermIdFromURI(uri), xdNode, lastModificationDate);
-        }
-        else if (SyncEntityKind.EXPERIMENT.getLabel().equals(entityKind))
+        } else if (SyncEntityKind.EXPERIMENT.getLabel().equals(entityKind))
         {
             parseExperimentMetaData(xpath, extractPermIdFromURI(uri), xdNode, lastModificationDate);
-        }
-        else if (SyncEntityKind.SAMPLE.getLabel().equals(entityKind))
+        } else if (SyncEntityKind.SAMPLE.getLabel().equals(entityKind))
         {
             parseSampleMetaData(xpath, extractPermIdFromURI(uri), xdNode, lastModificationDate);
-        }
-        else if (SyncEntityKind.DATA_SET.getLabel().equals(entityKind))
+        } else if (SyncEntityKind.DATA_SET.getLabel().equals(entityKind))
         {
             parseDataSetMetaData(xpath, extractDataSetCodeFromURI(uri), xdNode, lastModificationDate);
-        }
-        else if (SyncEntityKind.MATERIAL.getLabel().equals(entityKind))
+        } else if (SyncEntityKind.MATERIAL.getLabel().equals(entityKind))
         {
             parseMaterialMetaData(xpath, extractMaterialCodeFromURI(uri), xdNode, lastModificationDate);
         }
@@ -287,13 +282,11 @@ public class ResourceListParser
         {
             ds = new NewContainerDataSet();
             ds.setDataSetKind(ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetKind.CONTAINER);
-        }
-        else if (dsKind.equals(DataSetKind.PHYSICAL.toString()))
+        } else if (dsKind.equals(DataSetKind.PHYSICAL.toString()))
         {
             ds = new NewExternalData();
             ds.setDataSetKind(ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetKind.PHYSICAL);
-        }
-        else
+        } else
         {
             throw new IllegalArgumentException(dsKind + " data sets are currently not supported");
         }
@@ -318,13 +311,11 @@ public class ResourceListParser
             if (nullAllowed == false)
             {
                 throw new IllegalArgumentException(attrName + " cannot be empty in Resource List");
-            }
-            else
+            } else
             {
                 return null;
             }
-        }
-        else
+        } else
         {
             return val.trim();
         }
@@ -349,7 +340,16 @@ public class ResourceListParser
         SampleIdentifier sampleIdentifier = SampleIdentifierFactory.parse(sampleIdentifierStr);
         SpaceIdentifier spaceLevel = sampleIdentifier.getSpaceLevel();
         String originalSpaceCode = spaceLevel.getSpaceCode();
-        return new SampleIdentifier(new SpaceIdentifier(nameTranslator.translate(originalSpaceCode)), sampleIdentifier.getSampleCode());
+        SpaceIdentifier translatedSpaceIdentifier = new SpaceIdentifier(nameTranslator.translate(originalSpaceCode));
+
+        if (sampleIdentifier.isProjectLevel())
+        {
+            return new SampleIdentifier(new ProjectIdentifier(translatedSpaceIdentifier, sampleIdentifier.getProjectLevel().getProjectCode()),
+                    sampleIdentifier.getSampleCode());
+        } else
+        {
+            return new SampleIdentifier(translatedSpaceIdentifier, sampleIdentifier.getSampleCode());
+        }
     }
 
     private ExperimentIdentifier getExperimentIdentifier(String experimentIdentifierStr)
@@ -392,14 +392,20 @@ public class ResourceListParser
         return new ProjectIdentifier(createSpaceIdentifier(space), code);
     }
 
-    private SampleIdentifier createSampleIdentifier(String code, String space)
+    private SampleIdentifier createSampleIdentifier(String code, String project, String space)
     {
         if (space == null)
         {
             return new SampleIdentifier(nameTranslator.translate(code));
+        } else if (project == null)
+        {
+            SpaceIdentifier spaceIdentifier = createSpaceIdentifier(space);
+            return new SampleIdentifier(spaceIdentifier, code);
+        } else
+        {
+            ProjectIdentifier projectIdentifier = createProjectIdentifier(project, space);
+            return new SampleIdentifier(projectIdentifier, code);
         }
-        SpaceIdentifier spaceIdentifier = createSpaceIdentifier(space);
-        return new SampleIdentifier(spaceIdentifier, code);
     }
 
     private SpaceIdentifier createSpaceIdentifier(String space)
@@ -499,7 +505,7 @@ public class ResourceListParser
                 val = nameTranslator.translate(val);
                 val = translateMaterialIdentifier(val);
             }
-            
+
         }
         propertyType.setCode(translatedCode);
         property.setPropertyType(propertyType);
@@ -507,7 +513,8 @@ public class ResourceListParser
         return property;
     }
 
-    private String translateMaterialIdentifier(String value) {
+    private String translateMaterialIdentifier(String value)
+    {
         if (StringUtils.isBlank(value))
         {
             return null;
@@ -526,6 +533,7 @@ public class ResourceListParser
         }
         return new MaterialIdentifier(code, typeCode).toString();
     }
+
     private void parseExperimentMetaData(XPath xpath, String permId, Node xdNode, Date lastModificationDate)
     {
         String code = extractCode(xdNode);
@@ -547,14 +555,21 @@ public class ResourceListParser
         String code = extractCode(xdNode);
         String type = extractType(xdNode);
         String experiment = extractAttribute(xdNode, "experiment", true);
+        String project = extractAttribute(xdNode, "project", true);
         String space = extractSpace(xdNode, true);
         SampleType sampleType = new SampleType();
         sampleType.setCode(type);
-        
-        SampleIdentifier identifier = createSampleIdentifier(code, space);
+
+        if (false == StringUtils.isBlank(project) && StringUtils.isBlank(space))
+        {
+            throw new IllegalArgumentException("Sample with perm id '" + permId + "' has 'project' attribute specified but 'space' attribute empty");
+        }
+
+        SampleIdentifier identifier = createSampleIdentifier(code, project, space);
         String expIdentifier = null;
         ExperimentIdentifier experimentIdentifier = getExperimentIdentifier(experiment);
-        if (experimentIdentifier != null) {
+        if (experimentIdentifier != null)
+        {
             expIdentifier = experimentIdentifier.toString();
         }
         NewSample newSample = new NewSample(identifier.toString(), sampleType, null, null,
@@ -565,6 +580,10 @@ public class ResourceListParser
         {
             newSample.setDefaultSpaceIdentifier(null);
         }
+        if (space != null && project != null)
+        {
+            newSample.setProjectIdentifier(createProjectIdentifier(project, space).toString());
+        }
         IncomingSample incomingSample = data.new IncomingSample(newSample, lastModificationDate);
         data.getSamplesToProcess().put(permId, incomingSample);
         incomingSample.setHasAttachments(hasAttachments(xpath, xdNode));
@@ -618,4 +637,3 @@ public class ResourceListParser
 // System.out.print(url_node.item(ji).getNodeName() + ":" + url_node.item(ji).getAttributes().getNamedItem("kind"));
 // System.out.println();
 // }
-
diff --git a/integration-tests/source/systemtest/testcase.py b/integration-tests/source/systemtest/testcase.py
index 46f88c94aa3bacbaba1dc152bdf25905666041d8..98057ed32855f9c67840bcbaddf79fe05bc985e7 100644
--- a/integration-tests/source/systemtest/testcase.py
+++ b/integration-tests/source/systemtest/testcase.py
@@ -500,6 +500,9 @@ class OpenbisController(_Controller):
     def setDssMaxHeapSize(self, maxHeapSize):
         self._setMaxHeapSize("datastore_server/etc/datastore_server.conf", maxHeapSize)
         
+    def enableProjectSamples(self):
+        self.asProperties['project-samples-enabled'] = "true"
+        
     def assertFileExist(self, pathRelativeToInstallPath):
         """
         Asserts that the specified path (relative to the installation path) exists.
diff --git a/integration-tests/templates/test_openbis_sync/core-plugins/openbis1/1/dss/drop-boxes/entity-reg/dropbox.py b/integration-tests/templates/test_openbis_sync/core-plugins/openbis1/1/dss/drop-boxes/entity-reg/dropbox.py
index 40ffc6e2d65caf072711bbc061a818c5e1fd1998..444da915f9977ea78e1967e1351798ffe01cdaff 100644
--- a/integration-tests/templates/test_openbis_sync/core-plugins/openbis1/1/dss/drop-boxes/entity-reg/dropbox.py
+++ b/integration-tests/templates/test_openbis_sync/core-plugins/openbis1/1/dss/drop-boxes/entity-reg/dropbox.py
@@ -9,9 +9,9 @@ def process(transaction):
     spaceCode = "SYNC"
     p1 = transaction.createNewProject("/%s/P1" % spaceCode)
     exp1 = transaction.createNewExperiment(p1.getProjectIdentifier() + "/E1", "UNKNOWN")
-    container_smp = transaction.createNewSample("/%s/S1" % spaceCode, "UNKNOWN")
+    container_smp = transaction.createNewSample(p1.getProjectIdentifier() + "/S1", "UNKNOWN")
     container_smp.setExperiment(exp1)
-    component_samp = transaction.createNewSample("/%s/S2" % spaceCode, "UNKNOWN")
+    component_samp = transaction.createNewSample(p1.getProjectIdentifier() + "/S2", "UNKNOWN")
     component_samp.setContainer(container_smp)
     component_samp.setExperiment(exp1)
      
diff --git a/integration-tests/templates/test_openbis_sync/openbis_test_openbis_sync_openbis1.sql b/integration-tests/templates/test_openbis_sync/openbis_test_openbis_sync_openbis1.sql
index b6a61ef663522be2d35a2945a346df9d1edb9a84..b55da16f6110a504198363e951ba3148a92bcb3b 100644
--- a/integration-tests/templates/test_openbis_sync/openbis_test_openbis_sync_openbis1.sql
+++ b/integration-tests/templates/test_openbis_sync/openbis_test_openbis_sync_openbis1.sql
@@ -3181,7 +3181,7 @@ SELECT pg_catalog.setval('experiment_code_seq', 1, false);
 -- Name: experiment_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
 --
 
-SELECT pg_catalog.setval('experiment_id_seq', 2, true);
+SELECT pg_catalog.setval('experiment_id_seq', 3, true);
 
 
 --
@@ -3261,6 +3261,7 @@ COPY experiment_types (id, code, description, modification_timestamp, validation
 COPY experiments_all (id, perm_id, code, exty_id, pers_id_registerer, registration_timestamp, modification_timestamp, proj_id, del_id, orig_del, is_public, pers_id_modifier, version) FROM stdin;
 1	20161010123907442-1	DEFAULT	1	1	2016-10-10 12:39:07.442524+02	2016-10-10 12:39:07.442524+02	1	\N	\N	f	\N	0
 2	20161010125004951-1	DEMO-EXP-HCS	2	3	2016-10-10 12:50:06.971812+02	2016-10-10 12:50:06.971812+02	2	\N	\N	f	3	0
+3	20180901153109866-10	SYNC_EXPERIMENT	1	2	2018-09-01 15:31:09.866062+02	2018-09-01 15:31:59.013934+02	3	\N	\N	f	2	0
 \.
 
 
@@ -3496,7 +3497,7 @@ SELECT pg_catalog.setval('operation_executions_id_seq', 1, false);
 -- Name: perm_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
 --
 
-SELECT pg_catalog.setval('perm_id_seq', 8, true);
+SELECT pg_catalog.setval('perm_id_seq', 14, true);
 
 
 --
@@ -3539,7 +3540,7 @@ SELECT pg_catalog.setval('post_registration_dataset_queue_id_seq', 3, true);
 -- Name: project_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
 --
 
-SELECT pg_catalog.setval('project_id_seq', 2, true);
+SELECT pg_catalog.setval('project_id_seq', 3, true);
 
 
 --
@@ -3568,6 +3569,7 @@ SELECT pg_catalog.setval('project_relationships_history_id_seq', 4, true);
 COPY projects (id, perm_id, code, space_id, pers_id_leader, description, pers_id_registerer, registration_timestamp, modification_timestamp, pers_id_modifier, version) FROM stdin;
 1	20161010123907442-1	DEFAULT	1	\N	\N	1	2016-10-10 12:39:07.442524+02	2016-10-10 12:39:07.442524+02	\N	0
 2	20161010125006971-8	TEST-PROJECT	2	\N	A demo project	3	2016-10-10 12:50:06.971812+02	2016-10-10 12:50:06.971812+02	3	0
+3	20180901153055950-9	SYNC_PROJECT	3	\N	\N	2	2018-09-01 15:30:55.950159+02	2018-09-01 15:31:59.013934+02	2	0
 \.
 
 
@@ -3660,7 +3662,7 @@ SELECT pg_catalog.setval('sample_code_seq', 1, false);
 -- Name: sample_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
 --
 
-SELECT pg_catalog.setval('sample_id_seq', 2, true);
+SELECT pg_catalog.setval('sample_id_seq', 12, true);
 
 
 --
@@ -3757,8 +3759,12 @@ COPY sample_types (id, code, description, is_listable, generated_from_depth, par
 --
 
 COPY samples_all (id, perm_id, code, expe_id, saty_id, registration_timestamp, modification_timestamp, pers_id_registerer, del_id, orig_del, space_id, samp_id_part_of, pers_id_modifier, code_unique_check, subcode_unique_check, version, proj_id) FROM stdin;
-1	20161010123907442-1	DEFAULT	1	1	2016-10-10 12:39:07.442524+02	2016-10-10 12:39:07.442524+02	1	\N	\N	1	\N	\N	DEFAULT,-1,-1,1	\N	0	\N
-2	20161010125004966-2	PLATE	2	4	2016-10-10 12:50:06.971812+02	2016-10-10 12:50:06.971812+02	3	\N	\N	2	\N	3	PLATE,-1,-1,2	\N	0	\N
+1	20161010123907442-1	DEFAULT	1	1	2016-10-10 12:39:07.442524+02	2016-10-10 12:39:07.442524+02	1	\N	\N	1	\N	\N	DEFAULT,-1,1,1	\N	0	1
+2	20161010125004966-2	PLATE	2	4	2016-10-10 12:50:06.971812+02	2016-10-10 12:50:06.971812+02	3	\N	\N	2	\N	3	PLATE,-1,2,2	\N	0	2
+9	20180901153134072-11	SYNC_SPACE_SAMPLE	\N	1	2018-09-01 15:31:34.072585+02	2018-09-01 15:31:34.072585+02	2	\N	\N	3	\N	2	SYNC_SPACE_SAMPLE,-1,-1,3	\N	0	\N
+10	20180901153144176-12	SYNC_PROJECT_SAMPLE	\N	1	2018-09-01 15:31:44.176582+02	2018-09-01 15:31:44.176582+02	2	\N	\N	3	\N	2	SYNC_PROJECT_SAMPLE,-1,3,3	\N	0	3
+11	20180901153159013-13	SYNC_EXPERIMENT_SAMPLE	3	1	2018-09-01 15:31:59.013934+02	2018-09-01 15:31:59.013934+02	2	\N	\N	3	\N	2	SYNC_EXPERIMENT_SAMPLE,-1,3,3	\N	0	3
+12	20180901153310961-14	SHARED_SAMPLE	\N	1	2018-09-01 15:33:10.961996+02	2018-09-01 15:33:10.961996+02	2	\N	\N	\N	\N	2	SHARED_SAMPLE,-1,-1,-1	\N	0	\N
 \.
 
 
diff --git a/integration-tests/test_openbis_sync.py b/integration-tests/test_openbis_sync.py
index 2d5c3850b9ee64b485cc0979475d81d0da7143c1..a8604bd721b0c92ab420cc19172b4a1a8ba8fd98 100755
--- a/integration-tests/test_openbis_sync.py
+++ b/integration-tests/test_openbis_sync.py
@@ -38,6 +38,7 @@ class TestCase(systemtest.testcase.TestCase):
         '''create data source openbis (openbis1)'''
         self.installOpenbis(instanceName ='openbis1', technologies = ['screening'])
         openbis1 = self.createOpenbisController('openbis1')
+        openbis1.enableProjectSamples()
         openbis1.setDummyAuthentication()
         openbis1.setDataStoreServerUsername('etlserver1')
         openbis1.setDataStoreServerProperty("host-address", "https://localhost")
@@ -63,6 +64,7 @@ class TestCase(systemtest.testcase.TestCase):
         '''create harvester openbis (openbis2)'''
         self.installOpenbis(instanceName ='openbis2', technologies = ['screening', 'proteomics'])
         openbis2 = self.createOpenbisController('openbis2', port = openbis2_port)
+        openbis2.enableProjectSamples()
         openbis2.setDummyAuthentication()
         openbis2.setDataStoreServerUsername('etlserver2')
         openbis2.setDataStoreServerPort(openbis2_dss_port)
@@ -113,6 +115,7 @@ class TestCase(systemtest.testcase.TestCase):
         openbis2_dss_port = '8446'
 
         openbis1=self.createOpenbisController(instanceName = 'openbis1', dropDatabases=False)
+        openbis1.enableProjectSamples()
         openbis1.setDummyAuthentication()
         self.installDataSourcePlugin(openbis1, openbis1_dss_port)
          
@@ -133,6 +136,7 @@ class TestCase(systemtest.testcase.TestCase):
 #        openbis1.dropAndWait("ENTITY_REGISTRATION", "openbis-sync-entity-reg")
 
         openbis2 = self.createOpenbisController(instanceName = 'openbis2', port = openbis2_port, dropDatabases=False)
+        openbis2.enableProjectSamples()
         openbis2.setDataStoreServerPort(openbis2_dss_port)
         openbis2.setOpenbisPortDataStoreServer(openbis2_port)
         openbis2.setDataStoreServerProperty("host-address", "https://localhost")
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/Sample.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/Sample.java
index 1bef10b2218321f49cb247db70a3fec53e4d0177..134a4500a4f011bd85a31e61ace6fcff34db5e68 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/Sample.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/sample/Sample.java
@@ -27,6 +27,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IModificationD
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IModifierHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IParentChildrenHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IPermIdHolder;
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IProjectHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IPropertiesHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IRegistrationDateHolder;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IRegistratorHolder;
@@ -60,7 +61,7 @@ import java.util.Set;
  * Class automatically generated with DtoGenerator
  */
 @JsonObject("as.dto.sample.Sample")
-public class Sample implements Serializable, IAttachmentsHolder, ICodeHolder, IDataSetsHolder, IEntityTypeHolder, IExperimentHolder, IIdentifierHolder, IMaterialPropertiesHolder, IModificationDateHolder, IModifierHolder, IParentChildrenHolder<Sample>, IPermIdHolder, IPropertiesHolder, IRegistrationDateHolder, IRegistratorHolder, ISpaceHolder, ITagsHolder
+public class Sample implements Serializable, IAttachmentsHolder, ICodeHolder, IDataSetsHolder, IEntityTypeHolder, IExperimentHolder, IIdentifierHolder, IMaterialPropertiesHolder, IModificationDateHolder, IModifierHolder, IParentChildrenHolder<Sample>, IPermIdHolder, IProjectHolder, IPropertiesHolder, IRegistrationDateHolder, IRegistratorHolder, ISpaceHolder, ITagsHolder
 {
     private static final long serialVersionUID = 1L;
 
@@ -236,6 +237,7 @@ public class Sample implements Serializable, IAttachmentsHolder, ICodeHolder, ID
 
     // Method automatically generated with DtoGenerator
     @JsonIgnore
+    @Override
     public Project getProject()
     {
         if (getFetchOptions() != null && getFetchOptions().hasProject())
diff --git a/pybis/src/python/HOWTO_UPLOAD_TO_PYPI b/pybis/src/python/HOWTO_UPLOAD_TO_PYPI
new file mode 100644
index 0000000000000000000000000000000000000000..ce852ad73ea5793e70c0a3c0331cefff0d373ed1
--- /dev/null
+++ b/pybis/src/python/HOWTO_UPLOAD_TO_PYPI
@@ -0,0 +1,15 @@
+# create a python2 / python 3 universal distribution
+python setup.py bdist_wheel --universal
+
+# create a pure python distribution, which only works for a specific python version
+python setup.py bdist_wheel
+
+# create a source-distribution
+python setup.py sdist
+
+# see distributions
+ls -la dist/
+
+# upload distribution(s) to pypi
+twine upload dist/*
+
diff --git a/pybis/src/python/dist/PyBIS-1.6.7.tar.gz b/pybis/src/python/dist/PyBIS-1.6.7.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..87450e47cc59ec49207dbd9aeb369c9ddb3c16b5
Binary files /dev/null and b/pybis/src/python/dist/PyBIS-1.6.7.tar.gz differ
diff --git a/pybis/src/python/pybis/__init__.py b/pybis/src/python/pybis/__init__.py
index 6ff689482a5d6c51cdc7a8550c2e4d39d9c2491e..80b8b723b85e0929352d6dde571590a859c97c69 100644
--- a/pybis/src/python/pybis/__init__.py
+++ b/pybis/src/python/pybis/__init__.py
@@ -1,6 +1,6 @@
 __author__ = 'Swen Vermeul'
 __email__ = 'swen@ethz.ch'
-__version__ = '1.6.5'
+__version__ = '1.6.7'
 
 from . import pybis
 from .pybis import Openbis
diff --git a/pybis/src/python/pybis/semantic_annotation.py b/pybis/src/python/pybis/semantic_annotation.py
index 657ca773f80bfe2fbd42c3377c62a82dd41fb30e..73d5152c9e1c85173a5f4ad37a29687983994043 100644
--- a/pybis/src/python/pybis/semantic_annotation.py
+++ b/pybis/src/python/pybis/semantic_annotation.py
@@ -116,6 +116,33 @@ class SemanticAnnotation():
         self._openbis.delete_entity(entity='SemanticAnnotation', id=self.permId, reason=reason)
         if VERBOSE: print("Semantic annotation successfully deleted.")
 
+    def _repr_html_(self):
+        attrs = [ 'permId', 'entityType', 'propertyType', 'predicateOntologyId', 'predicateOntologyVersion', 'predicateAccessionId', 'descriptorOntologyId', 'descriptorOntologyVersion', 'descriptorAccessionId', 'creationDate',
+        ]
+
+        html = """
+            <table border="1" class="dataframe">
+            <thead>
+                <tr style="text-align: right;">
+                <th>attribute</th>
+                <th>value</th>
+                </tr>
+            </thead>
+            <tbody>
+        """
+
+        for attr in attrs:
+            html += "<tr> <td>{}</td> <td>{}</td> </tr>".format(
+                attr, getattr(self, attr, '')
+            )
+
+        html += """
+            </tbody>
+            </table>
+        """
+        return html
+        
+
     def __repr__(self):
         headers = ['attribute', 'value']
         lines = []
diff --git a/pybis/src/python/setup.py b/pybis/src/python/setup.py
index 3c4803574588c1c7d397833dcb1f201bbecc7ec4..99dce1042463091561987bf5b0cb895d2d1d616d 100644
--- a/pybis/src/python/setup.py
+++ b/pybis/src/python/setup.py
@@ -9,7 +9,7 @@ from setuptools import setup
 
 setup(
     name='PyBIS',
-    version= '1.6.5',
+    version= '1.6.7',
     description='openBIS connection and interaction, optimized for using with Jupyter',
     url='https://sissource.ethz.ch/sispub/pybis/',
     author='Swen Vermeul |  ID SIS | ETH Zürich',