I've created a DataObject called Service and I've got a many_many relationship to RelatedServices as follows:
class Service extends DataObject {
private static $db = array (
'Name' => 'Varchar',
'Description' => 'Varchar',
);
private static $many_many = array (
'RelatedServices' => 'RelatedService'
);
public function getCMSFields() {
$fields = FieldList::create(TabSet::create('Root'));
$services = $this->get()->where("\"Service\".\"Name\" != '$this->Name'")->map('ID', 'Name')->toArray();
$fields->addFieldsToTab('Root.Main', array(
TextField::create('Name'),
TextField::create('Description'),
ListBoxField::create('RelatedServices', 'Related services')->setMultiple(true)->setSource($services)
));
return $fields;
}
}
and:
class RelatedService extends DataObject {
private static $db = array (
'Name' => 'Varchar',
);
private static $belongs_many_many = array (
'RelatedServices' => 'RelatedService'
);
}
This is being used in a ModelAdmin and the service works right including the related services text area, however it doesn't save. I did it previously that it was in a seperate tab in the CMS and had RelatedService have and admin section which looking through the DB looked like it worked however I thought it was an unnecessary section so tried to make it all in one and now no longer saves to the DB.
You don't need the 'RelatedService' class, as you are referencing to the class you are working in. So relating to the class 'Service' itself would make more sense.
The reason why your code won't work is because you have got your relations mixed up.
class Service extends DataObject{
private static $db = array (
'Name' => 'Varchar',
'Description' => 'Varchar',
);
private static $many_many = array (
'RelatedServices' => 'Service'
);
private static $belongs_many_many = array (
'ParentServices' => 'Service'
);
public function getCMSFields() {
$fields = FieldList::create(TabSet::create('Root'));
$services = $this->get()->where("\"Service\".\"Name\" != '$this->Name'")->map('ID', 'Name')->toArray();
$fields->addFieldsToTab('Root.Main', array(
TextField::create('Name'),
TextField::create('Description'),
ListBoxField::create('RelatedServices', 'Related services')->setMultiple(true)->setSource($services)
));
return $fields;
}
}
Related
I followed the tutorial on https://www.silverstripe.org/learn/lessons/working-with-data-relationships-has-many?ref=hub to create some featured items for my homepage. But somehow I missed one piece, because I get this error
[Error] Uncaught Exception: No has_one found on class 'HomePageFeatured', the has_many relation from 'HomePage' to 'HomePageFeatured' requires a has_one on 'HomePageFeatured'
HomePage.php
<?php
/**
* Defines the HomePage page type
*/
class HomePage extends Page {
// private static $db = array(
// );
// private static $has_one = array(
// );
private static $has_many = array (
'Featured' => 'HomePageFeatured'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Featured', GridField::create(
'Featured',
'Hervorgehobene Produkte',
$this->Featured(),
GridFieldConfig_RecordEditor::create()
));
return $fields;
}
private static $icon = "themes/hstheme/images/treeicons/home";
}
class HomePage_Controller extends Page_Controller {
}
HomePageFeatured.php
<?php
/**
* Holds the featured items from the Homepage
*/
class HomePageFeatured extends DataObject {
private static $db = array(
'Title' => 'Varchar',
'Description' => 'Text'
);
private static $has_one = array(
'Photo' => 'Image',
'HomePageFeatured' => 'HomePageFeatured'
);
public function getCMSFields() {
$fields = FieldList::create(
TextField::create('Title'),
TextareaField::create('Description'),
$uploader = UploadField::create('Photo')
);
$uploader->setFolderName('featured-photos');
$uploader->getValidator()->setAllowedExtensions(array('png','gif','jpeg','jpg'));
return $fields;
}
}
As I understand, the problem is the $has_one from HomePageFeatured.php. But it has a reference from HomePageFeatured.
HomePageFeatured needs a has_one of 'HomePage'
class HomePageFeatured extends DataObject {
private static $has_one = array(
'Photo' => 'Image',
'Parent' => 'HomePage',
);
}
Unable to locate in the SilverStripe Documentation how to have a DataObject Model inject a collection of default records on /dev/build
Anybody able to point me in the right direction
This is what I currently have, and obviously I would like to inject pre-configured options into this aptly named Configuration model for my Module.
class Configuration extends DataObject
{
private static $db = array(
'Option' => 'Varchar',
'Value' => 'Varchar'
);
private static $summary_fields = array(
'Option' => 'Option',
'Value' => 'Value',
);
}
Thanks in advance for any direction/pointers.
UPDATE
I was turned onto SiteConfig by #Barry below
However in following his practice, requireDefaultRecords() is not injecting defaults
Note: I have since revisited /dev/build?flush
class RMSConfiguration extends DataExtension
{
private static $db = array(
'username' => 'Varchar',
'password' => 'Varchar',
'agent_id' => 'Varchar(15)',
'client_id' => 'Varchar(15)',
'testMode' => 'Int(1)',
'timezone' => 'Varchar',
'apiUrl' => 'Varchar(255)'
);
public function updateCMSFields(FieldList $fields)
{
$fields->addFieldsToTab(
"Root.RMSConfig",
array(
TextField::create('username', 'RMS Username'),
TextField::create('password', 'RMS Password'),
TextField::create('agent_id', 'RMS Agent ID'),
TextField::create('client_id', 'RMS Client ID'),
TextField::create('apiUrl', 'API Url'),
CheckboxField::create("testMode", 'Toggle Test Mode'),
DropdownField::create("timezone", 'Timezone', static::$timezones)
)
);
}
public function requireDefaultRecords()
{
parent::requireDefaultRecords();
$arrOptions = array(
'timezone' => 'Australia/Sydney',
'apiUrl' => 'https://api.example.com.au/',
'testMode' => 0
);
foreach ($arrOptions as $strOption => $strValue) {
if (!$configuration = self::get()->filter('Option', $strOption)->first()) {
$configuration = self::create(array( 'Option' => $strOption ));
}
$configuration->Value = $strValue;
$configuration->write();
}
}
/**
* List of timezones supported by PHP >=5.3.x
*
* #var array
*/
public static $timezones = array(
"Africa/Abidjan",
"Africa/Accra",
"Africa/Addis_Ababa",
"Africa/Algiers",
...
...
"Zulu"
);
}
Using the function requireDefaultRecords in the DataObject - this is called during every dev/build.
Note: First check if the option exists to prevent duplicates as this will be called every time you dev build.
class Configuration extends DataObject {
private static $db = array(
'Option' => 'Varchar',
'Value' => 'Varchar'
);
private static $summary_fields = array(
'Option' => 'Option',
'Value' => 'Value',
);
function requireDefaultRecords() {
parent::requireDefaultRecords();
$arrOptions = array(
'Option1' => 'Value1',
'Option2' => 'Value2',
'Option3' => 'Value3',
);
foreach ($arrOptions as $strOption => $strValue) {
if (!$configuration = Configuration::get()->filter('Option',$strOption)->first())
$configuration = Configuration::create(array('Option' => $strOption));
$configuration->Value = $strValue;
$configuration->write();
}
}
}
One final comment is that there is a module for SiteConfig which is used by SilverStripe, most modules and where I would recommend you put configuration values like this instead.
If you do choose SiteConfig then please see the function populateDefaults and documentation for it's use, this is an example...
/**
* Sets the Date field to the current date.
*/
public function populateDefaults() {
$this->Date = date('Y-m-d');
parent::populateDefaults();
}
(if the above is used in an extensions it might need $this->owner->Date instead of $this->Date)
The above function isn't needed if all the values are static, instead it will read them just from this array (again within DataObject)
public static $defaults = array(
'Option1' => 'Value1',
'Option2' => 'Value2'
);
This works on any DataObject as well, but as SiteConfig manages one record and this populates that record once upon creation this is much more convenient for to use instead of requireDefaultRecords.
I got a many to many relationship between Products and Categories using Silverstripes' ORM. All is working except when I try to add a Category for a product the choices for Category is shown by user id instead of Category name. I tried to map ID to name but did not work. Below is what I tried, what am I missing?
class Product extends DataObject {
private static $db = array(
'ProductName' => 'Varchar(32)',
);
private static $many_many = array (
'Category' => 'Category'
);
}
class Category extends DataObject {
private static $db = array(
'Category' => 'Varchar(32)',
);
public function searchableFields() {
return array (
'Category' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Category',
'field' => 'TextField'->setSource(
$this::get()->map('ID','Category')
)
)
);
}
private static $belongs_many_many = array (
'Product' => 'Product'
);
}
When Silverstripe needs to display a shorthand for a DataObject, it will call the getTitle() method on that DataObject.
getTitle() will first check if the DataObject has a Title field and return that value if it does.
If your DataObject doesn't have Title field, it will try searching for a Name field.
If it can't find a Name field either, it will default to returning the ID of your DataObject, which is probably what is happening for you.
How to fix your specific example
There's 2 ways you can fix your specific example:
Rename your DataObjects DB fields to Name or Title
Override the getTitle() method on your 2 DataObjects
Solution #1
class Product extends DataObject {
private static $db = array(
'Name' => 'Varchar(32)',
);
private static $many_many = array (
'Category' => 'Category'
);
}
class Category extends DataObject {
private static $db = array(
'Name' => 'Varchar(32)',
);
public function searchableFields() {
return array (
'Category' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Category',
'field' => 'TextField'->setSource(
$this::get()->map('ID','Category')
)
)
);
}
private static $belongs_many_many = array (
'Product' => 'Product'
);
}
Solution #2
class Product extends DataObject {
private static $db = array(
'ProductName' => 'Varchar(32)',
);
private static $many_many = array (
'Category' => 'Category' );
public function getTitle() {
return $this->ProductName;
}
}
class Category extends DataObject {
private static $db = array(
'Category' => 'Varchar(32)',
);
public function searchableFields() {
return array (
'Category' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Category',
'field' => 'TextField'->setSource(
$this::get()->map('ID','Category')
)
)
); }
private static $belongs_many_many = array (
'Product' => 'Product' );
public function getTitle() {
return $this->Category;
}
}
Which solution is best?
In your specific case, I would go with solution #1, because your DB fields are functionally name/title fields.
I would use solution #2 if my DataObject's title needs to use many fields. Let's say you a have Person DataObject with a first name and a last name:
public function getTitle() {
return $this->FirstName . ' ' . $this->LastName;
}
I have two user groups Administrator and Inhaltsautoren
My LandingPage has a Tab Teaser with a gridField. The normal user can not see the entries and i dont know why?
I cant find something for setting the permissions for Inhaltsautoren. Has someone an idea why there are no entries in the gridField?
Teaser.php
<?php
class Teaser extends DataObject {
private static $db = array (
'Title' => 'Varchar',
'Description' => 'HTMLText'
);
private static $has_one = array (
'Photo' => 'Image',
'Link' => 'Link'
);
private static $many_many = array(
'Tags' => 'Tag'
);
private static $summary_fields = array (
'GridThumbnail' => '',
'Title' => 'Titel',
'Description' => 'Beschreibung'
);
public function getGridThumbnail() {
if($this->Photo()->exists()) {
return $this->Photo()->SetWidth(100);
}
return "(no image)";
}
public function getCMSFields() {
$fields = FieldList::create(
TextField::create('Title'),
$tags = TagField::create('Tags','Tags',Tag::get(),$this->Tags()),
HTMLEditorField::create('Description', 'Beschreibung'),
LinkField::create('LinkID', 'Weiterleitung'),
$uploader = UploadField::create('Photo')
);
$tags->setShouldLazyLoad(true); // tags should be lazy loaded
$tags->setCanCreate(true); // new tag DataObjects can be created
$uploader->setFolderName('teaser');
$uploader->getValidator()->setAllowedExtensions(array('png','jpeg','jpg'));
return $fields;
}
}
and my LadingPage.php
$fields->addFieldToTab('Root.Teaser', $gridField = GridField::create(
'Teasers',
'Landing Page Teaser',
$this->Teasers(),
GridFieldConfig_RecordEditor::create()
));
$gridField->getConfig()->getComponentByType("GridFieldDataColumns")->setFieldCasting(array("Description"=>"HTMLText->BigSummary"));
Use canView() on your dataobject and check inside this function if your user is allowed to see this object or not.
public function canView($member = null) {
return Permission::check('ADMIN', 'any');
}
I am having some problems with the admin of Silverstripe. I defined a database model (see class definitions below), and after I do a dev/build everything is looking as expected. When I try to add a new "package" all the "has one" fields are there with a drop down (see screen shot 1). I also built an importer which imports these packages. When run, everything is looking fine, except when you open a package. Then 'Festival' is correctly coupled. You can see the name, and you can select the drop down. "Troupe", on the other hand, has mysteriously converted to an input field that only shows the id of the record in the other table (See screen shot 2).
Does anyone know what is happening here? Is there something that triggers this behaviour that I am unaware off? Is there something wrong with my code (yes, but related to this problem? ;-))? I have checked the structure of the tables, and there is nothing suspicious there...
Before:
After:
Package.php
class Package extends DataObject {
public static $db = array(
'Number' => 'Int',
'Title' => 'Varchar(255)',
'Description' => 'HTMLText',
'Credits' => 'HTMLText',
);
public static $has_many = array(
'Events' => 'Event',
);
public static $many_many = array(
'Genres' => 'Genre',
);
public static $has_one = array(
'Festival' => 'Festival',
'Troupe' => 'Troupe',
);
}
class PackageAdmin extends ModelAdmin {
public static $managed_models = array('Package'); // Can manage multiple models
static $url_segment = 'packages'; // Linked as /admin/packages/
static $menu_title = 'Packages';
}
Troupe.php
class Troupe extends DataObject {
public static $db = array(
"Name" => "Varchar(255)",
"Description" => "HTMLText",
"Url" => "Varchar(255)",
);
public static $has_many = array(
'Packages' => 'Package.Troupe',
);
}
class TroupeAdmin extends ModelAdmin {
public static $managed_models = array('Troupe','Package'); // Can manage multiple models
static $url_segment = 'troupes'; // Linked as /admin/troupes/
static $menu_title = 'Troupes';
}
Festival.php
class Festival extends DataObject {
public static $db = array(
'Name' => 'Varchar(255)',
'Description' => 'HTMLText'
);
public static $has_many = array(
'Packages' => 'Package.Festival'
);
}
class FestivalAdmin extends ModelAdmin {
public static $managed_models = array('Festival','Package'); // Can manage multiple models
static $url_segment = 'festivals'; // Linked as /admin/festivals/
static $menu_title = 'Festivals';
}
You probably shouldn't only rely on the admin scaffolding, and use getCMSFields on your DataObjects to customize what happen in the CMS. In you case, a simple replace of the Troupe dropdown could work, adding this to your Package class:
function getCMSFields()
{
$fields = parent::getCMSFields();
$troupeList = Troupe::get()->map()->toArray();
$troupeSelect = DropdownField::create('TroupeID', 'Troupe')->setSource($troupeList);
$fields->replaceField('TroupeID', $troupeSelect);
return $fields;
}
This is quite minimalist and a lot more could me customised.