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',
);
}
Related
I'm extending ModelAdmin to manage a DataObject and I have a $has_many relationship that is managed by another DataObject. I'd like to manage this object on another tab but am lost as to how I'd add it. My basic code:
ApplicationAdmin.php:
class Applications extends ModelAdmin {
private static $menu_title = 'Applications';
private static $url_segment = 'apps';
private static $managed_models = array (
'Application'
);
}
Application.php
class Application extends DataObject {
private static $db = array(
'Name' => "varchar"
);
private static $has_many = array(
'Documents' => 'Document',
);
public function getCMSFields() {
$fields = FieldList::create(
TextField::create('Name'),
);
return $fields;
}
}
Document.php
class Document extends DataObject {
private static $db = array(
'Title' => "varchar",
);
private static $has_one = array(
"Document" => "File",
"Application" => "Application"
);
public function getCMSFields () {
$fields = FieldList::create(
TextField::create('Title'),
$doc = new UploadField('Document')
);
$doc->getValidator()->setAllowedExtensions(array('pdf', 'docx'));
return $fields;
}
}
Basically I'd like to manage the documents for this entry under Root.Documents tab.
You can use a GridField to handle the relation between Application and Document, and place that field on its own tab if you wish. Example:
# Application.php
public function getCMSFields() {
$fields = parent::getCMSFields();
$nameField = TextField::create('Name');
$documentsField = GridField::create(
'Documents',
'Documents',
$this->Documents(),
GridFieldConfig_RelationEditor::create()
);
$fields->addFieldToTab(
'Root.Main',
$nameField
);
$fields->addFieldToTab(
'Root.Documents',
$documentsField
);
return $fields;
}
Your code contains a some typos in the Document class (the classname has .php in it, getValidator should be getValidator()), and that class also needs a $has_one = ['Application' => 'Application']; for the relation to work.
I have a product catalog. There are:
Dzial.php - category page
class Dzial extends Page {
...
static $has_many = array(
'Marka' => 'Marka'
);
function getCMSFields() {
...
$gridField = new GridField("Marka", "marki:", $this->Marka(), $gridFieldConfig);
$fields->addFieldToTab("Root.Marki", $gridField);
PodDzial.php - subcategory page
class PodDzial extends Page {
.....
static $has_many = array(
'Kategoria' => 'Kategoria',
'Produkt' => 'Produkt'
);
function getCMSFields() {
....
$gridField = new GridField("Produkt", "Produkty:", $this->Produkt(), $gridFieldConfig);
$fields->addFieldToTab("Root.Produkty", $gridField);
$gridField2 = new GridField("Kategoria", "Kategoria:", $this->Kategoria(), $gridFieldConfig);
$fields->addFieldToTab("Root.Kategorie", $gridField2);
Produkt.php - product - DataObjects which belong to PodDzial.php
class Produkt extends DataObject {
.....
static $has_one = array (
'PodDzial' => 'PodDzial',
'Marka' => 'Marka',
'Kategoria' => 'Kategoria'
);
Kategoria.php - type of product- which belong to PodDzial (has_one) and to Produkt (has_many)
class Kategoria extends DataObject {
....
private static $has_one = array( 'PodDzial' => 'PodDzial' );
private static $has_many = array ('Produkt' => 'Produkt' );
Marka.php - brand of product- which belong to Dzial (has_one) and to Produkt (has_many)
class Marka extends DataObject {
...
private static $has_one = array( 'Dzial' => 'Dzial' );
private static $has_many = array ('Produkt' => 'Produkt');
I work on custom search for Produkt's on PodDzial page.
The code which doesn't work properly:
class Produkt extends DataObject {
...
public function getDefaultSearchContext() {
$fields = new FieldList(
DropdownField::create(
'MarkaID',
'Marka',
Marka::get()->map('ID','Title')
)->setEmptyString('--Wybierz marke--') ,
DropdownField::create(
'KategoriaID',
'Kategoria',
Kategoria::get()->filter(array('PodDzialID' => $this->PodDzialID))->map('ID','Title')
)->setEmptyString('--Wybierz kategorie--')
);
$filters = array(
'MarkaID' => new ExactMatchFilter('MarkaID'),
'KategoriaID' => new ExactMatchFilter('KategoriaID')
);
return new SearchContext(
$this->class,
$fields,
$filters
);
}
Both dropdown fields are working bad. They should list Kategoria's only from its PodDzial holder and Marka's from parent Dzial. Marka's field shows all Marka even from other categories now. Kategoria show nothing.
I use those same dropdowns on fieldlist. Kategoria's work ok. It shows only data from given PodDzial. Markas are on parent holder (Dzial) and I cant access it there too :
class Produkt extends DataObject {
....
$xxx = 'LEFT JOIN `SiteTree` ON `SiteTree`.ParentID=`Marka`.DzialID';
function getCMSFields(){
$fields = FieldList::create(
TabSet::create("Root",
Tab::create("Main",
....
DropdownField::create(
'MarkaID',
'Marka',
Marka::get()->filter($xxx, array('DzialID' => 'ParentID'))->map('ID','Title')
)->setEmptyString('-- None --'),
DropdownField::create(
'KategoriaID',
'Kategoria',
Kategoria::get()->filter(array('PodDzialID' => $this->PodDzialID))->map('ID','Title')
)->setEmptyString('-- None --')
Can anyone help?
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'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;
}
}
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');
}