Silverstripe 3.1 - Export Dataobject with all relations? - php

I need to export the whole data of an dataobject. Database fields and relations.
private static $db = array (
'URLSegment' => 'Varchar(255)',
'SKU' => 'Text',
'Title' => 'Text',
'Price' => 'Text',
'Content' => 'HTMLText',
'ItemConfig' => 'Int',
'Priority' => 'Int'
);
private static $has_one = array (
'Visual' => 'Image'
);
private static $has_many = array (
'Sizes' => 'ShopItem_Size',
'Colors' => 'ShopItem_Color'
);
private static $many_many = array (
'Visuals' => 'Image',
'Categories' => 'ShopCategory'
);
I added everything to getExportFields(). But as expected the result for the relations is "ManyManyList" or "HasManyList"
public function getExportFields() {
return array(
'SKU' => 'SKU',
'Title' => 'Title',
'Price' => 'Price',
'Content' => 'Content',
'ItemConfig' => 'ItemConfig',
'Visual' => 'Visual',
'Visuals' => 'Visuals',
'Sizes' => 'Sizes',
'Colors' => 'Colors',
'Categories' => 'Categories'
);
}
Is it possible to create such an export?
Thank you in advance

You can use any method name instead of a field/relation name in the exportFields array.
In your ModelAdmin class
public function getExportFields() {
return array(
'SKU' => 'SKU',
'Title' => 'Title',
'CategoryNames' => 'Categories'
);
}
and just have a method with that name on the DataObject, returning the relation data as a string:
public function CategoryNames(){
$catNames = array();
foreach($this->Categories() as $cat){
$catNames[] = $cat->getField('Title');
}
//use a separator that won't break the CSV file
return join("; ", $catNames);
}
I think this is even way better than the modeladmin creating magic fields in your CSV file,
that would make it inconsistent...

Related

CakePHP - saving to more than two models using save associated

I am trying something more complicated. I have an Item which stores all general items, I have a Product which is an item and I have a Good which is a product and a item. So I have a form for entering value for the good and it shall save to all model related tables (items, products, goods). The reason for so many tables is because all tables shall have an id which is used later, example: product shall have its id which is used later for selling. Here is the controller:
public function add() {
$this->load();
if ($this->request->is('post')) {
$this->Item->create();
$this->request->data['Item']['code'] = $finalCode;
$this->request->data['Item']['is_deleted'] = false;
$item = $this->Item->save($this->request->data);
if(!empty($item)){
$this->request->data['Product']['item_id'] = $this->Item->id;
$this->request->data['Good']['item_id'] = $this->Item->id;
debug($this->request->data['Product']['item_id']);
debug($item);
$this->Item->Product->save($this->request->data);
$this->request->data['Good']['pid'] = $this->Product->id;
$this->Item->Good->save($this->request->data);
}
if($this->Good->validationErrors || $this->Item->validationErrors || $this->Product->validationErrors){
//ERRORS
}
else{
//FAILS
}
}
}
EDIT: I have changed the controller and now the Item is never saved but Product and Good is saved and they are all mapped well, ids are ok but Item is not even in the db, althrough Product and Good have item_id set to a value which should be next in the db.
class Good extends AppModel {
public $belongsTo = array(
'Item' => array(
'className' => 'Item',
'foreignKey' => 'item_id',
),
'Product' => array(
'className' => 'Product',
'foreignKey' => 'pid',
)
);
}
class Product extends AppModel {
public $hasOne = array(
'Good' => array(
'className' => 'Good',
'foreignKey' => 'pid',
'dependent' => false,
),
);
}
class Item extends AppModel{
public $hasMany = array(
'Good' => array(
'className' => 'Good',
'foreignKey' => 'item_id',
'dependent' => false,
),
'Product' => array(
'className' => 'Product',
'foreignKey' => 'item_id',
'dependent' => false,
),
);
}
Even the debugged $item looks ok but is not saved:
array(
'Item' => array(
'name' => 'Microcontrollers',
'description' => 'Wire Jumpers Female-to-Female 30 cm',
'weight' => '22',
'measurement_unit_id' => '7',
'item_type_id' => '29',
'code' => 'GOD-34',
'is_deleted' => false,
'modified' => '2019-10-22 12:37:53',
'created' => '2019-10-22 12:37:53',
'id' => '120'
),
'Good' => array(
'status' => 'development',
'hts_number' => '8473 30 20',
'tax_group' => '20%',
'eccn' => 'EAR99',
'release_date' => array(
'month' => '10',
'day' => '22',
'year' => '2019',
'hour' => '10',
'min' => '10',
'meridian' => 'am'
),
'is_for_distributors' => '1'
),
'Product' => array(
'project' => 'neqwww'
)
)
I think the problem is with your code at the lines where you are saving the data.
$this->Item->create();
$this->Good->create();
$this->Product->create();
What is $this? If you create a item with "$this" and later try to create a "product", "$this" will not have the item_id.
Try using something like this to save the item created.
$item = $this->Item->create();
Then, with that $item created, you could create a $product with the $item->id
Update:
From cakephp documentation.
// Create: id isn't set or is null
$this->Recipe->create();
$this->Recipe->save($this->request->data);
// Update: id is set to a numerical value
$this->Recipe->id = 2;
$this->Recipe->save($this->request->data);
You must use $this->Item->save($data) to save the information into database.
https://book.cakephp.org/2.0/en/models/saving-your-data.html
Maybe the method create() is a bit unclear. It is used to restart the model state.
So, it would be
$item_saved = $this->Item->save($data['Item']);
$data['Product']['item_id'] = $this->Item->getLastInsertId();
$product_saved = $this->Product->save($data['Product']);
Edit 2:
Maybe it is because you didn't use create() before save the Item. Try this please:
$this->Item->create();
$item_saved = $this->Item->save($data['Item']);
$data['Product']['item_id'] = $this->Item->getLastInsertId();
$product_saved = $this->Product->save($data['Product']);

Use setQueriedColumns on GridField

I have a dataobject which I want to use on a GridField on a page but I want to limit the columns displayed. I used setQueriedColumns() to list the fields I wanted but it is still displaying the default $summary_fields from the dataobject.
MyActivity dataobject:
class MyActivity extends DataObject{
private static $db = array(
'Title' => 'Varchar(255)',
'URLSegment' => 'Varchar(512)',
'IsPublished' => 'Boolean',
'IsPublic' => 'Boolean',
'IsBooked' => 'Boolean',
'MaxDuration' => 'Int',
'PricePoint' => 'Int',
'Summary' => 'HTMLText',
'Body' => 'HTMLText',
'Sort' => 'Int'
);
private static $has_one = array(
'FileAttachment' => 'File'
);
private static $summary_fields = array(
'Title' => 'Name',
'URLSegment' => 'URLSegment',
'IsPublished' => 'Published',
'IsBooked' => 'Booked',
'Events.Count' => 'List of Events',
'Categories.Count' => ' of Categories'
);
static $has_many = array(
'Events' => 'MyEvent'
);
static $belongs_many_many = array(
'Categories' => 'MyCategory'
);
...
}
MyActivityPage:
class MyActivityPage extends Page{
public function getCMSFields(){
$fields = parent::getCMSFields();
$GridFieldConfig = GridFieldConfig_RecordEditor::create();
$fields->addFieldToTab('Root.Courses',
GridField::create(
'FileAttachment',
'Activity List',
MyActivity::get()->filter(['IsPublished' => 1])
->setQueriedColumns([
'Title',
'URLSegment',
'IsPublished'
]),
$GridFieldConfig
)
);
return $fields;
}
...
}
After thorough searching, I got what I'm looking for. Apparently, we can set the columns using GridFieldConfig then limit the fields by overriding the $summary_fields using the setDisplayFields() method in the GridFieldDataColumns object.
This could be handy to people who are looking for similar solution.
$gridField = GridField::create(
'FileAttachment',
'Activity List',
MyActivity::get()->filter(['IsPublished' => 1]),
$GridFieldConfig
)
$gridField->getConfig()
->getComponentByType('GridFieldDataColumns')
->setDisplayFields([
'Title' => 'Title',
'URLSegment' => 'URLSegment',
'IsPublished' => 'IsPublished'
]);
$fields->addFieldToTab('Root.Courses',$gridField);`

Associating an item to multiple other items (of a different class) using Prestashop's backoffice

Having just arrived at Prestashop 1.5, I am making a very simple module: a video of the week, associated with multiple products that need to appear right next to it.
I decided to start from the Backoffice. Right now, I can view, add, edit and remove all the Video entries but I'm a bit lost on how to map the N-N association between a video and its related products... The lack of documentation isn't helping either.
Any ideas how to pull this off?
Here's a bit of my code, the Video class is defined by:
class Video extends ObjectModel {
public $id_video;
public $title;
public $url;
public $active;
public static $definition = array(
'table' => 'video',
'primary' => 'id_video',
'multilang' => false,
'fields' => array(
'id_video' => array(
'type' => ObjectModel :: TYPE_INT
),
'title' => array(
'type' => ObjectModel :: TYPE_STRING,
'required' => true
),
'url' => array(
'type' => ObjectModel :: TYPE_STRING,
'required' => true
),
'active' => array(
'type' => ObjectModel :: TYPE_BOOL,
'required' => true
)
),
);
(...)
and the AdminVideo class is here:
class AdminVideoController extends ModuleAdminController {
public function __construct()
{
$this->table = 'video';
$this->className = 'Video';
$this->lang = false;
$this->fields_list['id_video'] = array(
'title' => $this->l('ID'),
'align' => 'center',
);
$this->fields_list['title'] = array(
'title' => $this->l('Title'),
'width' => 'auto'
);
$this->fields_list['url'] = array(
'title' => $this->l('URL'),
'width' => 'auto'
);
$this->fields_list['active'] = array(
'title' => $this->l('Active'),
'width' => '70',
'align' => 'center',
'active' => 'status',
'type' => 'bool',
'orderby' => false
);
parent::__construct();
}
public function postProcess()
{
parent::postProcess();
}
public function renderList()
{
$this->addRowAction('edit');
$this->addRowAction('delete');
$this->addRowAction('details');
return parent::renderList();
}
public function renderForm()
{
if (!($obj = $this->loadObject(true)))
return;
$this->fields_form = array(
'legend' => array(
'title' => $this->l('This weeks video'),
'image' => '../img/admin/world.gif'
),
'input' => array(
array(
'type' => 'text',
'label' => $this->l('Nome'),
'name' => 'title',
'size' => 33,
'required' => true,
'desc' => $this->l('Title')
),
array(
'type' => 'text',
'label' => $this->l('URL'),
'name' => 'url',
'size' => 33,
'required' => true,
'desc' => $this->l('Video URL')
),
array(
'type' => 'radio',
'label' => $this->l('Active:'),
'name' => 'active',
'required' => false,
'class' => 't',
'is_bool' => true,
'values' => array(
array(
'id' => 'active_on',
'value' => 1,
'label' => $this->l('Enabled')
),
array(
'id' => 'active_off',
'value' => 0,
'label' => $this->l('Disabled')
)
),
'desc' => $this->l('Only one video can be active at any given time')
),
)
);
if (Shop::isFeatureActive())
{
$this->fields_form['input'][] = array(
'type' => 'shop',
'label' => $this->l('Shop association:'),
'name' => 'checkBoxShopAsso',
);
}
$this->fields_form['submit'] = array(
'title' => $this->l(' Save '),
'class' => 'button'
);
if (!($obj = $this->loadObject(true)))
return;
return parent::renderForm();
}
}
One other thing: would it be possible to add a preview of the video inside the backoffice? I tried to echo YouTube's embed code, but it gets inserted even before the header. Is there a clean way of doing this or do I have to use some jQuery trickery? I was basically doing an echo of YT's embed code just before the end of postProcess().
Thanks in advance!
The simplest way to associate the videos to the products is by adding a "products" text field in your "video" table to store a comma separated list of the ids of the associated products (eg.: 1,10,27). Even if it's a bit rudimentary, it should work.
Alternatively, you could use a table like this:
create table video_product (
id_association int not null auto_increment,
id_video int,
id_product int,
primary key (id_association)
);
The problem with this solution is that the PrestaShop ObjectModel core does not provide any method to automatically update or delete the related tables (at least as far as I know), so you have to insert the code to manage the "video_product" table in your "Video" class.
If you want an example of how to do this, you should look at the classes/Product.php script, which manages the product table and all its related tables (categories, tags, features, attachments, etc.).
To have an idea of how the Prestashop database is structured, have a look at the docs/dbmodel.mwb file, which contains the schema of the database; this file can be viewed by using the MySQL Workbench application.

Using yii with dynamic data and highcharts

Hi everybody thanks for reading i was wandering how you can insert dynamic data into the highcharts extension for example i have the highcharts extension as follows (location of code =>Reprting/index):
$this->Widget('ext.highcharts.HighchartsWidget', array(
'options'=>array(
'credits' => array('enabled' => false),
'title' => array('text' => $graphTitle),
'xAxis' => array(
'categories' => array('Apples', 'Bananas', 'Oranges')
),
'yAxis' => array(
'title' => array('text' => 'Fruit eaten')
),
'series' => array(
array('name' => 'Jane', 'data' => array(3, 6, 7)),
array('name' => 'John', 'data' => array(5, 7, 3))
) )));
And i have the following code in the controller :
public function actionIndex()
{
$model= $this->loadModel();
$dataProvider=new CActiveDataProvider('Reporting');
$graphTitle= 'Price Per Product';
$this->render('index',array(
'dataProvider'=>$dataProvider, 'graphTitle'=>$graphTitle, 'model'=>$model,
));
}
And the following code is the model :
class Reporting extends CActiveRecord
{
public static function model($className=__CLASS__)
{
return parent::model($className);
}
/**
* #return string the associated database table name
*/
public function tableName()
{
return '{{price}}';
}
/**
* #return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('id_product, id_channel', 'required'),
array('id_product, id_channel', 'numerical', 'integerOnly'=>true),
array('price_min, price_max', 'numerical'),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('id_price, id_product, id_channel, price_min, price_max', 'safe', 'on'=>'search'),
);
}
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'idChannel' => array(self::BELONGS_TO, 'Channel', 'id_channel'),
'idProduct' => array(self::BELONGS_TO, 'Product', 'id_product'),
);
}
public function attributeLabels()
{
return array(
'id_price' => __('Id Price'),
'id_product' => __('Id Product'),
'id_channel' => __('Id Channel'),
'price_min' => __('Price Min'),
'price_max' => __('Price Max'),
);
}
public function search()
{
$criteria=new CDbCriteria;
$criteria->compare('id_price',$this->id_price);
$criteria->compare('id_product',$this->id_product);
$criteria->compare('id_channel',$this->id_channel);
$criteria->compare('price_min',$this->price_min);
$criteria->compare('price_max',$this->price_max);
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
}
How do i put this all together to achieve a dynamically driven graph
One sample for you:
<?php
$xAxis = array(1,2,3);
$yAxis = array(4,5,6);
$this->Widget('ext.highcharts.HighchartsWidget',
array(
'id' => 'something',
'options'=> array(
'chart' => array(
'defaultSeriesType' => 'bar',
'style' => array(
'fontFamily' => 'Verdana, Arial, Helvetica, sans-serif',
),
),
'title' => array(
'text' => 'title',
),
'xAxis' => array(
'title' => array(
'text' => 'xTitle,
),
'categories' => $xAxis,
'labels' => array(
'step' => 1,
'rotation' => 0,
'y' => 20,
),
),
'yAxis' => array(
'title' => array(
'text' => 'yTitle,
),
),
'series' => array(
array(
'name' => 'seriesName',
'data' => $yAxis,
'shadow' => false,
)
)
)
)
);
?>
To customize it, you'll have to build your own $yAxis, $xAxis arrays, and modify title and settings. For more info, take a look at the official Highcharts doc.

Working with saveAssociated method in CakePHP

I'm trying to work with saveAssociated method in CakePHP without great success, the method seems to work partially, in facts I have three model in the save process:
Character, which $hasMany > Property and Label so this is the Character model:
class Character extends AppModel {
public $name = 'Character';
public $belongsTo = 'User';
public $hasMany = array (
'Property' => array (
'dependent' => true
)
);
public $hasOne = array (
'Label' => array (
'dependent' => true
)
);
public $validate = array (
'name' => array (
'required' => true,
'rule' => array('between', 0, 100),
'message' => 'This is the error message for "name" field'
),
'description' => array (
'allowEmpty' => true,
'rule' => array('between', 0, 100),
'message' => 'This is the error message for "description" field'
)
);
}
this is the data I get from the form:
array(
'Character' => array(
'name' => 'Character name',
'description' => 'Character description',
'image' => 'http://url.com/image.jpg'
),
'Label' => array(
'name' => 'Basic attributes',
'value' => 'Value'
),
'Property' => array(
(int) 0 => array(
'name' => 'Strenght',
'value' => '15'
)
)
)
then in my CharactersController i do this to saveAssociated data:
class CharactersController extends AppController {
public $uses = array ('Character', 'Property', 'Label');
public function add () {
if (!empty($this->request->data)) {
$this->Character->saveAssociated($this->request->data);
}
debug ($this->request->data);
}
}
The Character data is saved successfully but not Label and Property, where I'm wrong?
Have you declared the other ends of the Model Association in the other two Models? Have a good look at this page. Might make life easier if you use a $hasAndBelongsToMany association.
Try the saveAll method instead of saveAssociated.

Categories