Uncaught LogicException: byID can't be called on an UnsavedRelationList - silverstripe

I am creating a model_admin menu to manage orders. The 'Order' data object has a Has-Many relationship to 'Package' and the 'Package' has a Has-Many relationship to 'Product'.
I am using gridfield relation editor in Order and Package to add/link new 'Package' and 'Product'. So basically the process is to click on 'Add Order' then click on 'Add Package' then click on 'Add Product' and then fill in the product information.
Until this point, everything works fine, but when I entered all the product fields and clicked on create an error 'Uncaught LogicException: byID can't be called on an UnsavedRelationList' is thrown.
I understand this is because the 'Package' and 'Order' has not been created so it can not associate the products with them.
Of course I can create the Order first, then create the package, then add the 'Product', then everything works fine. However this is not intuitive and it is supposed to add products to packages and then create orders. I don't know if there is any way to implement my requirement, or if using gridfields within model_admin form is not the right solution for this.
Looking forward to any suggestions. Thank you.

This usually means that you're trying to do something to a has-many or many-to-many relationship before the core record is saved. Check you onBeforeWrite handlers. I often find it's helpful to hide the gridfields for related lists if $this->isInDB() is false.

Related

How do I query a Drupal Shortcut (8.x/9.x) by its URL or Menu Path?

We're rolling out an update to an in-house Drupal installation profile, and one of the menu paths that is used frequently is getting changed. Most of our installations reference that menu path in a shortcut (via the "Shortcut" module in core). In an update hook, we'd like to be able to query for those shortcuts and update them.
It feels like this should be straightforward, but for some reason we're finding it difficult to query for shortcuts by their url. We can query them by title, but that seems fragile (since the title could be different between installations, might be different by localization, etc.).
We tried the following, but this lead to the error message 'link' not found:
// This does NOT work.
$shortcuts_needing_update =
\Drupal::entityTypeManager()
->getStorage('shortcut')
->loadByProperties([
'link' => [
'internal:/admin/timeline-management',
],
]);
// This works, but is fragile.
$shortcuts_needing_update =
\Drupal::entityTypeManager()
->getStorage('shortcut')
->loadByProperties([
'title' => 'My shortcut',
]);
Based on the code in \Drupal\shortcut\Entity\Shortcut::baseFieldDefinitions() and \Drupal\shortcut\Controller\ShortcutSetController::addShortcutLinkInline() it's obvious that Shortcut entities have a property called link that can be set like an array containing a uri key, yet it does not seem possible to query by this property even though it's a base field.
Looking at the database, it appears that Drupal stores the URL in a database column called link__uri:
TL;DR That means that this works:
$shortcuts_needing_update =
\Drupal::entityTypeManager()
->getStorage('shortcut')
->loadByProperties([
'link__uri' => 'internal:/admin/old/path',
]
);
Read on if you want to know the subtle reason why this is the case.
Drupal's database layer uses pluggable "table mapping" objects to tell it how to map an entity (like a Shortcut) to one or more database tables and database table columns. The logic for generating a column name for a field looks like this in the default table mapping (\Drupal\Core\Entity\Sql\DefaultTableMapping):
As shown above, if a field indicates it allows "shared" table storage, and the field has multiple properties (uri, title, etc.), then the mapping flattens the field into distinct columns for each property, prefixed by the field name. So, a Shortcut entity with link => ['uri' => 'xyz']] becomes the column link__uri with a value of xyz in the database.
You don't see this often with entities like nodes, which is why this seems strange here. I'm usually accustomed to seeing a separate database table for things like link fields. That's because nodes and other content entities don't usually allow shared table storage for their fields.
How does the mapping determine if a field should use shared table storage? That logic looks like this:
So, the default table mapping will use shared table storage for a field only under specific circumstances:
The field can't have a custom storage handler (checks out here since shortcuts don't provide their own storage logic).
The field has to be a base field (shortcuts are nothing without a link, so that field is defined as a base field as mentioned in the OP).
The field has to be single-valued (checks out -- shortcuts have only one link).
The field must not have been deleted (checks out; again, what is a shortcut without a link field?).
This specific set of circumstances aren't often satisfied by nodes or other content entities, which is why it's a bit surprising here.
We can confirm this by using Devel PHP to ask the table mapping for shortcuts directly, with code like the following:
$shortcut_table_mapping =
\Drupal::entityTypeManager()
->getStorage('shortcut')
->getTableMapping();
$efm = \Drupal::service('entity_field.manager');
$storage_definitions = $efm->getFieldStorageDefinitions('shortcut');
$link_storage_definition = $storage_definitions['link'];
$has_dedicated_storage = $shortcut_table_mapping->requiresDedicatedTableStorage($link_storage_definition);
$link_column = $shortcut_table_mapping->getFieldColumnName($link_storage_definition, 'url');
dpm($has_dedicated_storage, 'has_dedicated_storage(link)');
dpm($link_column, 'link_column');
This results in the following:

Can you create a joined view from task list and related content?

I'm trying to create a view of approval tasks that also includes a column from the related form library. I have tried creating a linked data source between the tasks list and the form library, but have trouble finding much information on creating linked views with the task list.
I have tried:
http://deannaschneider.wordpress.com/2012/07/25/joining-the-task-list-with-related-content-in-a-dvwp/
without luck - it just tells me "there are no items to show in this view." which I assume means it couldn't be joined correctly with the specified table.
I am using the standard approval workflow.
Here is the closest solution I've found so far
1.) Create task form fields in SharePoint designer.
2.) Go into Approval(1) to add the task form fields.
3.) Click 'Change the behavior of a single task'. Add 'Set task field' action in the Before a task is assigned section to set the task form fields to get the value of Current Item:ID.
4.) Use the new task field to create your subview on your linked datasource
While not optimal - and it created me many different problems - I was able to create the view desired.
Hopefully someone will come up with a better solution.

Add a search form in a manyToMany Relation interface

How to implement a search form in a many to many relation between entities.
I want to search items from an entity before to add them to my other entity. I am using a long list of items (product) that i need to link to Shops and i can't use a simple listbox to select my items.
I need you to point me to a tutorial or any explaination to deal with this interface problem.
The goal is to use a minimum of javascript
I would suggest to create an view where you could select a category or define your search condition. And a second View where you only display products by the previously selected condition. In your second view you could use an entity Field Type ( http://symfony.com/doc/current/reference/forms/types/entity.html#query-builder ) and provide a custom query for the entities like:
use Doctrine\ORM\EntityRepository;
// ...
$builder->add('users', 'entity', array(
'class' => 'AcmeHelloBundle:Product',
'query_builder' => function(ProductRepository $er) {
return $er->createQueryBuilder('p')
->where('p.category = 1);
},
));
This solution doesn't require JavaScript at all.
I spent lot's of time trying to figure out the best solution to make the compromise between re-usability, performance and ergonomy and i found a nice solution
I did this way :
I created a custom form field that show a collection like entity field type but i pass field names that I want to show in a nice table :
->add('products','reflist',array(
'columns'=>array('name','cost','description'),
'actions'=>array('select'=>true,'remove'=>true),
'entityName'=>'VendorProductBundle:Product',
'searchForm'=> 'Vendor\ProductBundle\Form\ProductSearchType'
));
Then I created a generic searching service that takes in input the entity to search on. The result is sent in a popup paginated.
Finally, I created a controller related to my new field to manage actions like add, remove
Thats's it for the logic.
I can't really share the work since it is really dependent of my framework (depend of search service, layout,etc...)

Drupal subscriptions to taxonomy only

Playing around with the subscriptions module; i have some troubles getting it to send the right notification for right subscription.
Here's the situation :
I have a content-type of type 'work'; it has a cck-taxonomy field; when creating the content users choses one category in which his 'work' fulfills.
In user profil, under Categories (user/3/subscriptions/taxa) I choose two categories, lets say 'house work' and 'car work'.
When creating a new 'work' content I do not get the notification.
But, when manually select 'content-type' in user's profile of type 'work' I get the notification e-mail but independant to which 'category' i had chosen.
This is quite annoying since I only want the user to receive his notifications upon the taxonomy he has chosen, not for every new content of type 'work'.
Am I missing something obvious here ?
i have made it working fine for a taxonomy using the settings provided by the module. Concerning the rights i only allowed 'Subscribe to taxonomy terms '

migrating node references

I am working on a project to migrate a website from asp.net to drupal architecture. But the site content is very hierarchal and has a lot of references between entities.
for example: each content belongs to a category, and each category belongs to another category section. Now there may be another level of hierarchy even.
I am planning to make use of migrate module for migrating the database content and linking the migrated nodes via a node reference field.
But i am getting stuck with migrate module as i can't find a way to migrate the node reference field anywhere...
can anyone help me out with this...
Actually, it doesnt seem to be that hard .. in 2012. Yes, you have to keep track of source IDs versus import IDs, but the migrate module does this for you, in a neat little table. You can join that table in your source query, and update the node reference field with the nid of the .. referenced node. Ofcourse, the referenced nodes should have already been imported. If they werent, you can run an run an 'update' later and referenced nids get entered based on the latter imports too. In practice:
$query = Database::getConnection(
'default', 'mysourcedb'
)->select(
'mysourcetable','source'
)->fields('source', array(
'id',
'title',
'whatever'
'rel_rec_id'
)
);
$query->leftJoin('migrate_map_relimport','relmap','relmap.sourceid1=source.rel_rec_id');
$query->addField('relmap','destid1','rel_node_id');
The code above assumes you have a 'mysourcedb' with a 'mysourcetable' in it that refers to a 'rel_rec_id', and theres another import called RelImport that imports the rel table that rel_rec_id is refering to; it should have already run (or will run before you run an additional update). Do a migrate-status once you have the RelImport class to make sure the table exists.
To be able to make joins to the 'migrate_map_relimport' table, make sure map tables are written to the source database, not the drupal database. This is not always necessary, but here it is:
$this->map = new MigrateSQLMap(
$this->machineName,
array(
'id' => array(
'type' => 'int',
'unsigned' => true,
'not null' => true,
'alias' => 'source'
)
),
MigrateDestinationNode::getKeySchema(),
'mysourcedb' // connection to use to write map tables
);
and finally, assign the retrieved rel_node_id to your node reference:
$this->addFieldMapping( 'field_rel_node', 'rel_node_id' );
Yay, it is rocket science .. YMMV
As far as I know, you will not be able to do this entirely within the migrate module. You'll have to run a few queries directly in MySQL.
Essentially, you'll have to create an extra field in each content type to house their legacy ID's and an additional field for each legacy reference (in addition to the actual nodereference field). Load all the data into the content types (leave the nodereference field empty). Then once all of the entities are loaded in, you run mysql queries to populate the nodereference fields based on the legacy IDs and legacy reference fields. Once that's done you can safely delete these legacy fields.
Not the most elegant solution, but I've used it many times.
Caveats:
This is for Drupal 6; Drupal 7's fields implementation is totally different AFAIK.
For very large migrations, you might want to do your legacy field deletions through MySQL.
You might also take a look at the code for the Content Migrate module (more information at https://drupal.org/node/1144136). It's for migrating D6 Content Construction Kit (CCK) content to D7 Fields and it's been integrated with the References module.
It won't do what you need out of the box, as you're coming from an ASP.net site instead of a D6 site, but it may provide some clues.

Resources