Silverstripe assumes SiteTree relation is to Parent? - orm

My (partial) DataObject:
class InternalExternalLink extends DataObject {
private static $db = array(
'ExternalLink' => 'VarChar(256)',
'LinkLabel' => 'VarChar(256)',
"LinkType" => "Enum(array('Internal', 'External','Attachment'))"
);
private static $has_one = array(
'InternalLink' => 'SiteTree',
'Attachment' => 'File'
);
function getCMSFields() {
$fields = new FieldList(array(
$internal = DropdownField::create("InternalLinkID", "Choose a page", SiteTree::get()->map()->toArray())->setEmptyString("-- choose --"),
));
return $fields;
}
Add I add this to Page:
class Page extends SiteTree {
private static $has_many = array(
'Links' => 'InternalExternalLink'
);
function getCMSFields() {
$fields = parent::getCMSFields();
$gridField = new GridField('Links', 'Links', $this->Links(), GridFieldConfig_RecordEditor::create());
$fields->addFieldsToTab('Root.Main', $gridField);
return $fields;
}
The problem is when adding Links via the gridfield it automatically assumes that the Link.InternalLink is the parent page, rather than any page, and hides the page select drop down. E.g. if I am editing the about-us page then every Link dataobject I add via the gridfield automatically sets its InternalLink to the about-us page.
How do I change this assumption to allow me to select any page via the dropdown?

Try this:
1) Give the DataObject a "Parent" relation:
class InternalExternalLink extends DataObject {
private static $has_one = array(
'Parent' => 'DataObject',
'InternalLink' => 'SiteTree',
'Attachment' => 'File'
);
...
}
2) Specify "Parent" in the page's has_many:
class LinkTestPage extends SiteTree {
private static $has_many = array(
'Links' => 'InternalExternalLink.Parent'
);
...
}

The problem here is the relation on InternalExternalLink is to SiteTree whereas you're trying to define a relationship back to it on Page. As there's no has_one from InternalExternalLink to Page, and you're using a slightly older version of 3.1, the default has_one of Parent is looked for.
To solve this, you can either change the InternalLink relation to point to Page instead of SiteTree or use a DataExtension to add the has_many relation on to SiteTree.

Related

ReactiveUI - master/detail binding - de-select current record

I'm trying to implement master/detail using ReactiveUI for a list of attachments. Here's a simplified version of my view model:
public class AttachmentsViewModel : ReactiveObject, ISupportsActivation
{
private ReactiveList<Attachment> _attachments;
public ReactiveList<Attachment> Attachments
{
get => _attachments;
set => this.RaiseAndSetIfChanged( ref _attachments, value );
}
private IReactiveDerivedList<AttachmentViewModel> _attachmentViewModels;
public IReactiveDerivedList<AttachmentViewModel> AttachmentViewModels
{
get => _attachmentViewModels;
set => this.RaiseAndSetIfChanged(ref _attachmentViewModels, value );
}
private AttachmentViewModel _selected;
public AttachmentViewModel Selected
{
get => _selected;
set => this.RaiseAndSetIfChanged( ref _selected, value );
}
}
In my view, I have a ListView and a set of controls to let the user edit properties (e.g. a TextBox called AttachmentName). Here's what I would do in the view, based on the view model above:
public class AttachmentsView : IViewFor<AttachmentsViewModel>
{
public AttachmentsView()
{
InitializeComponent();
this.WhenActivated( d => {
this.OneWayBind( ViewModel, vm => vm.AttachmentViewModels, v => v.List.ItemsSource ).DisposeWith( d );
this.Bind( ViewModel, vm => vm.Selected, v => v.List.SelectedItem ).DisposeWith( d );
this.Bind( ViewModel, vm => vm.Selected.Name, v => v.AttachmentName.Text ).DisposeWith( d );
}
}
}
All of this works as expected, when I click on a row in the ListView, the controls in my panel change accordingly.
However, when I de-select the currently selected item, AttachmentName still displays the old value (instead of showing nothing). I'm assuming that the linq expression does not fire the property changed event because the binding is more than one property deep?
Is there any way around this? Maybe there's another way to achieve master-detail navigation/editing?
You can observe in your view the SelectedItem and change the AttachmentName:
this.WhenAnyValue(x => x.ViewModel.Selected).Where(x => x == null)
.Subscribe(selected => AttachmentName.Text == "").DisposeWith(d);

Phalcon query builder can't get joined table data

I have 2 table 'sanpham' and 'danhmuc'. I use phalcon query builder to get data from 2 tables.
$laytin = $this->modelsManager->createBuilder()
->from("sanpham")
->innerJoin('danhmuc','sanpham.danhmuc=danhmuc.sodanhmuc')
->where('sanpham.sosanpham = '.$id.'')
->getQuery()
->getSingleResult();
$breadcrumbs = array('/' => Tool::getTranslation()->_('trangchu'),"/Loai-san-pham/".$laytin->tendep."/".$laytin->sodanhmuc => $laytin->tendanhmuc,'' => $laytin->tieudesanpham );
The query runs, but $laytin->tendep, $laytin->sodanhmuc, $laytin->tendanhmuc in 'danhmuc' table doesn't display. Every column in 'sanpham' table (such as: $laytin->tieudesanpham) displays properly.
You can add specific columns with:
$this->modelsManager->createBuilder()->columns('danhmuc.tend‌​ep, danhmuc.sodanhmuc')
With this method you will have to add each column you want in your output. QueryBuilder docs.
Another method is to query the Sanpham model.
For example:
class Sanpham extends \Phalcon\Mvc\Model
{
public static function findSomething($something)
{
// this is your actual query, it replaces the queryBuilder
return self::query()
->where('sanpham.sosanpham = :id:', ['id' => $something])
->innerJoin('danhmuc', 'sanpham.danhmuc = danhmuc.sodanhmuc')
->execute()->getFirst();
}
public function initialize()
{
// define the relation to danhmuc
$this->belongsTo('danhmuc', 'danhmuc', 'sodanhmuc');
}
}
class Danhmuc extends \Phalcon\Mvc\Model
{
public function initialize()
{
// there are other options besides "hasMany", like "hasOne".
// this is your relation to sanpham
$this->hasMany('sodanhmuc', 'sanpham', 'danhmuc');
}
}
class YourController extends \Phalcon\Mvc\Controller
{
public function testAction()
{
// get your first record in Sanpham matching "12345"
$sanpham = Sanpham::findSomething(12345);
// from your Sanpham object, get the related danhmuc object.
// this works because we defined the relations (belongsTo and hasMany)
$danhmuc = $sanpham->getRelated('danhmuc');
// now you have access to the values of danhmuc via the $danhmuc variable
$breadcrumbs = [
'/' => Tool::getTranslation()->_('trangchu'),
"/Loai-san-pham/" . $danhmuc->tendep => $danhmuc->tendanhmuc,
'' => $danhmuc->tieudesanpham,
];
}
}
Check the Phalcon model docs for more info on this.

Customize pagination links in CLinkPager

I am using CLinkPager and need to customize the pagination links url.
Need to add #test in the url of the pagination links.
You can implement custom class extends CLinkPager and override createPageUrl() mentod there:
class MyLinkPager extends CLInkPager(){
public $linkHash = '';
protected function createPageUrl($page)
{
$url = $this->getPages()->createPageUrl($this->getController(),$page);
if($this->linkHash)
$url = $url.'#'.$this->linkHash;
return $url;
}
}
Put file with this class in extension folder and dont forget add this folder in import in config (main.php):
'import'=>array(
'application.extensions.*',
...
)
And further, for example in CGrigView configuration, set this pager class:
this->widget(
'zii.widgets.grid.CGridView',
array(
'dataProvider' => $dataProvider,
'pager'=>array(
'class'=>'MyLinkPager',
'linkHash'=>'test'
),
...
)
)

Silverstripe: Many to many relation to same class

I have a class "performance", and to each performance other performances can be linked as recommendations. After some trial and error I have something semi-working, using the ORM:
public static $many_many = array(
'Recommendations' => 'Performance'
);
public static $belongs_many_many = array(
'Recommendations' => 'Performance'
);
The above lets me specify recommendations, but in normal many to manies (between two classes), the relation the other way around is also available. Here it is not. Any clue on how to make the inverse relation visible on the "Performance" class without creating the inverse many-to-many and manually inserting the inverse relation there?
Update(May 22, 2014): For now I've come to the conclusion that there isn't an out of the box solution to this problem. I made the following minimal example, based on #FinBoWa's solution, and that shows the "Inverse component of Degree.InterestingDegreesReversed not found (Degree)":
class Degree extends DataObject {
private static $db = array(
"Name" => "Varchar(255)",
);
private static $many_many = array(
'InterestingDegrees' => 'Degree'
);
private static $belongs_many_many = array(
'InterestingDegreesReversed' => 'Degree.InterestingDegrees'
);
}
class DegreeAdmin extends ModelAdmin {
public static $managed_models = array('Degree');
static $url_segment = 'degrees';
static $menu_title = 'Degrees';
}
I also tried #g4b0's solution, but that has the serious drawback that it does not show the reverse relationship in the admin. For now I am using his solution, but it is not a real solution to the problem. Therefore I will not accept an answer for now...
You have to specify two different names for many_many and belongs_many_many, so you can access them. For example:
public static $many_many = array(
'RelatedRecommendations' => 'Performance'
);
public static $belongs_many_many = array(
'Recommendations' => 'Performance'
);
You should be able to use the dot notation in the relations.
One of our projects had degrees and we wanted to relate the interesting degrees using the same class:
private static $many_many = array(
'InterestingDegrees' => 'Degree'
);
private static $belongs_many_many = array(
'InterestingDegreesReversed' => 'Degree.InterestingDegrees'
);
This works if you add the editor manually when relating site trees.
But if you use model admin, you have to declare the fields manually otherwise you will get erros. So a working example if you are using model admin would be:
class Degree extends DataObject {
private static $db = array(
"Name" => "Varchar(255)",
);
private static $many_many = array(
'InterestingDegrees' => 'Degree'
);
private static $belongs_many_many = array(
'InterestingDegreesReversed' => 'Degree.InterestingDegrees'
);
public function getCMSFields() {
$fields = new FieldList();
$fields->add(new TextField("Name"));
// dont show the field untill we have something to relate to
if($this->ID){
//Interesting degrees
$gfc = GridFieldConfig_RelationEditor::create();
//Remove add and edit features
$gfc->removeComponentsByType('GridFieldAddNewButton');
$gfc->removeComponentsByType('GridFieldEditButton');
$gf = new GridField('InterestingDegrees', null, $this->InterestingDegrees(), $gfc);
$fields->add($gf);
}
return $fields;
}
}
class DegreeAdmin extends ModelAdmin {
public static $managed_models = array('Degree');
static $url_segment = 'degrees';
static $menu_title = 'Degrees';
}
I've gone through both ideas, and this is what I've come up with for a project I'm working on (I'm using the Brand model).
This is inspired by both solutions, the only additional item I'm adding is the onAfterWrite method. There I'm just looping through all brands, and adding the other side.
This takes for all brands to be edited through only the Brands tab. The other should be removed in getCMSFields like this: $fields->removeByName('RelatedBrands');.
<?php
class Brand extends DataObject {
private static $many_many = [
'Brands' => 'Brand' //part 1 of brand-brand relation
];
private static $belongs_many_many = [
'RelatedBrands' => 'Brand' //part 2 of brand-brand relation
];
public function onAfterWrite() {
parent::onAfterWrite();
foreach ($this->Brands() as $brand) {
$this->RelatedBrands()->add($brand);
}
}
}

Kohana (KO3) columns with ORM join

I'm trying to retrieve information from a database using Kohana ORM.
There are two relevant tables in my database:
branches
id smallint
parent_id smallint
name varchar
active int
branches_options
id mediumint
branche_id smallint
name varchar
customer_id int
With the following code I want to retrieve the information from the branches_options table
` $branchesOptions[] = ORM::factory('branches_option')
->where('branche_id', '=', $subBranche)
->join('branches', 'LEFT')->on('branches.id', '=', 'branches_options.branche_id')
->order_by('name')
->find_all()
->as_array();`
Now I want to see the value of branches.name in the result set, but I'm not sure how to do this in Kohana.
The code of the models is:
`class Model_Branche extends ORM
{
protected $_has_many = array(
"options" => array('model' => 'branches_option'),
"adwords_templates" => array ('model' => 'adwords_template')
);
public $result = array();`
and
`class Model_Branches_option extends ORM
{
protected $_has_many = array (
"keywords" => array('model' => 'branches_options_keyword')
);
protected $_has_and_belongs_to = array (
"adwords_templates" => array (
"model" => "adwords_template",
"through" => "branches_options_templates"
)
);
protected $_belongs_to = array ( "branche" => array () );`
Can this be done and if so, how?
You need to make some important changes to you models for this to work properly:
class Model_Branche extends ORM
{
protected $_has_many = array(
'options' => array(
'model' => 'branches_option',
'foreign_key' => 'branche_id'
)
);
}
And the Branches_Option model (it should be in model/branche/ folder):
class Model_Branches_Option extends ORM
{
protected $_belongs_to = array(
'branche' => array()
);
}
Now you can do something like that:
$options = ORM::factory('branche', $branche_id)
->options
->find_all();
foreach ($options as $option)
{
$branche_active = $option->branche->active;
$branche_name = $option->branch->name;
$option_name = $option->name;
}
One of the most important changes here is that we specify the foreign_key option in the $_has_many relationship. Since the ORM is using the Kohana Inflector helper it might not recognize it automatically (branches in singular form is branch and not branche).
If it doesn't work try specifying the $_table_name property for the same reason.
I'm trying to also grasp the concept here. In order to think like an ORM in this regard, you need to start with tables that the main tables reference as related information.
So in my world, I have a waste report, (model/waste/report) and there exists another table that has all the codes (model/waste/codes). so in the waste_reports table there's a column called code. That field might have 2E. 2E means nothing without the waste_codes table. The waste_codes table is (id, code, name).
I defined this relationship as such:
class Model_Waste_code extends ORM {
protected $_primary_key = "code";
protected $_has_one = array(
'waste_report' => array()
);
}
Then in my waste report model:
class Model_Waste_report extends ORM
{
protected $_belongs_to = array(
'codes' => array(
'model' => 'waste_code',
'foreign_key' => 'code'
)
);
}
To show different examples:
public function action_ormwaste() {
$test = ORM::factory('waste_report',76);
if ($test->loaded())
echo $test->codes->name;
echo "<hr noshade />";
$test = ORM::factory('waste_report')->find_all();
foreach($test as $row)
echo $row->codes->name . "<BR>";
}
Would output:
Error with image file (mirrored, etc)
-------------------------------------
Wrong Size
Extra Prints
Error with image file (mirrored, etc)
etc...
So in essense, the join on the data is handled on the fly. I'm using Kohana 3.2.
Thanks, that cleared me up.