Search In Realtion Fields

Hi,

I do not get the right search result in a tabular input view ( = batchUpdate), when I use a related field for search.

The search is working with "normal" fields of the table but after input of a search criteria in a related field I get all records of the data table.

I use the same search form, that I also use in /view/admin view CGridView. With CGridView I can search in each field of the form - also in related fields. Everything is working.

Therefore I assume, that the search form is ok and the search method in the model does the job.

I think, the bug must be in the controller code of my batchUpdate action. But I am not able to find the fault. By the way: The pagination works, also saving of data in that tabular input form works …




        public function actionBatchUpdate()

        {

            $model=new Noten();

            $criteria = new CDbCriteria();

 

            if(isset($_GET['Noten'])) {

		$model->unsetAttributes();

                $model->attributes = $_GET['Noten'];

            }


            $dataProvider =  $model->search();


            $criteria = $dataProvider->criteria;

            $count=$model->count($criteria);


            $pages=new CPagination($count);

            $pages->pageSize=6;

            $pages->applyLimit($criteria);


            $items = $dataProvider->data;


            if(isset($_POST['Noten'])) {

                $valid=true;

                foreach($items as $i=>$item)

                {

                    if(isset($_POST['Noten'][$i]))

                        $item->attributes=$_POST['Noten'][$i]; 

                        $valid=$item->validate() && $valid;

                        $item->save();

                }

            }


            $this->render('batchUpdate', array(

                'model' => $model,

                'items' => $items,

                'pages' => $pages,

            ));

        }



Thank you for hints.

Hi Ferdinand,

1.) Freut mich dass es hier mehr und mehr Österreicher ins Forum verschlägt :)

2.) Wenn du die search Methode deines Notenmodels verwendest, dann musst du diese aber auch erweitern wenn du über verknüpfte Datensätze suchen willst (related models). Zudem musst du in diesem Fall "eager loading" anwenden, damit die Datensätze immer MIT den verknüpften Datensätzen geladen werden und du dann darin filtern kannst. Klingt kryptisch, is aber eigentlich easy:

Erweitere deine search Methode um folgende Zeilen (ich nehm jetzt mal an dass das "related" Model ein Komponist ist und die Relation in deinem Notenmodel "composer" genannt wird):


$criteria->with = array( 'composer' );

$criteria->compare( 'composer.name', $this->composerName, true );

$this->composerName existiert ja noch nicht, deshalb fügst du diese variable zu deinem Notenmodell hinzu via


public $composerName;

Jetzt musst du diese Variable nur noch auf "safe" setzen (in deinen rules() im Notenmodell), damit sie mittels $notenModel->attributes gesetzt werden kann und du musst natürlich ein Feld in deinem Suchformular einbauen das den "composerName" setzt.

Ich hoff es hilft,

lg

Hannes

Hallo Hannes,

danke für deine Vorschläge. Angenehm, dass wir auf gut Deutsch reden können!

Eigentlich hatte ich ja das, was du vorschlägst alles gemacht. Die Suche funktioniert ja, wie gesagt, wenn sie auf der admin-Seite verwendet wird. War schwer als Yii Anfänger überhaupt einmal so weit zu kommen. Ich habe das mühsam mit Hilfe von Forenpostings zusammengezimmert.

Hier ist der Code der Suchfunktion aus dem Noten model:


public function search() {

    $criteria=new CDbCriteria;

    

    $criteria->with = array('Komponist','Verlag', 'Sammelheft');

    

    $criteria->compare('t.Id',$this->Id);

    $criteria->compare('Werk',$this->Werk,true);

    $criteria->compare('Besetzung',$this->Besetzung,true);

    

    $criteria->compare('Verlag_Nr',$this->Verlag_Nr,true);

    $criteria->compare('Name',$this->Name_Verlag,true);

    

    $criteria->compare('Reihe',$this->Reihe,true);

    $criteria->compare('Anmerkung',$this->Anmerkung,true);

    

    $criteria->compare('Titel',$this->Titel_Sammelheft,true);

    $criteria->compare('Zuname',$this->Name_Komponist,true);

    $criteria->compare('Vorname',$this->Name_Komponist,true,'OR');

    

    $data = new CActiveDataProvider($this, array(

            'criteria'=>$criteria,

            'sort'=>array(

                'defaultOrder'=>'Zuname ASC',

                'attributes'=>array('Name_Komponist'=>array('asc'=>'Zuname','desc'=>'Zuname DESC'),

                                    'Name_Verlag'=>array('asc'=>'Name','desc'=>'Name DESC'),

                                    'Titel_Sammelheft'=>array('asc'=>'Titel','desc'=>'Titel DESC'),

                                    '*'),

        )

    ));

    return $data;

    }

}

Die Variablen Name_Komponist, Name_Verlag, Titel_Sammelheft habe ich im Model gesetzt. Diese Variablen beziehen sich auf Felder der verknüpften Tabellen.

Aber jetzt stehe ich seit Tagen vor dem Rätsel, dass diese Suche zwar auf der admin Seite funktioniert, in einer anderen Ansicht, nämlich batchUpdate, funktioniert sie nur mit nicht relationalen Feldern. Ich komme nicht dahinter, warum.

Vielleicht kannst du mir Tipps geben. Danke jedenfalls für jede Mühe.

Gruß

Ferdinand

Hallo nochmal,

Es könnte auch ein Problem mit dem (falsch gesetzten) Szenario sein. Wenn du dir actionAdmin() ansiehst wirst du sehen, dass dort ein model mit


$model = new XYZModel("search");

instanziert wird. Man übergibt also das gewünschte Szenario (in diesem Fall "search") an den Konstruktor und setzt somit auch die Regeln die dadurch angewendet werden wenn die Attribute des Models gesetzt bzw. validiert werden. Im Klartext heisst das, dass in deiner rules() Methode im Notenmodell vermutlich sowas steht


array(

....

'Name_Komponist, Name_Verlag', 'safe', 'on' => 'search'

....

)

Das würde bedeuten dass über $model->attributes nur dann diese beiden Variablen gesetzt werden, wenn das Model zuvor mit "new Noten("search")" instanziert wurde (was bei dir nicht der Fall ist). Wenn die Regeln gar nicht definiert sind (also weder mit, noch ohne dem "on"=>"search" Teil), dann musst du sie definieren und dann in deinem Controller


$model=new Noten("search");

angeben, da die setAttributes() methode von CActiveRecord nur jene Variablen setzt die explizit als "safe" in den rules angegeben wurden ($model->attributes ist die Kurzform von $model->setAttributes(array()))

Hoff es hilft

Liebe Grüße,

Hannes

Ja, das hat geholfen :)

Die geänderte erste Zeile im Controller war der Schlüssel zur Lösung.


$model=new Noten('search');

Ich habe bei der Gelegenheit den Code noch etwas verschlankt:




        public function actionBatchUpdate()

        {

            $model=new Noten('search');

            if(isset($_GET['Noten'])) {

		$model->unsetAttributes();

                $model->attributes = $_GET['Noten'];

            }


            $dataProvider =  $model->search();

            $count=$model->count($dataProvider->criteria);


            $pages=new CPagination($count);

            $pages->pageSize=6;

            $pages->applyLimit($dataProvider->criteria);


            if(isset($_POST['Noten'])) {

                $valid=true;

                foreach($dataProvider->data as $i=>$item)

                {

                    if(isset($_POST['Noten'][$i]))

                        $item->attributes=$_POST['Noten'][$i]; 

                        $valid=$item->validate() && $valid;

                        $item->save();

                }

            }


            $this->render('batchUpdate', array(

                'model' => $model,

                'items' => $dataProvider->data,

                'pages' => $pages,

            ));

        }

Jetzt werden die Datensätze entsprechend den Suchkriterien angezeigt, auch bei der Suche in einem relationalen Feld.

Ich habe allerdings noch das Problem, dass die Pagination nicht funktioniert. Es wird immer nur die erste Seite angezeigt, egal ob das Suchergebnis aus allen Seiten oder nur aus einigen besteht. Das Pagination widget zeigt mir die Links zu der richtigen Anzahl von Seiten an, z.B.:


http://suserver/yii_notenverwaltung/index.php?r=noten/batchUpdate&page=8

aber bei Klick auf einen dieser Links wird trotzdem nur die erste Seite angezeigt.

Bitte noch einmal um Hilfe.

Liebe Grüße

Ferdinand

Da du einen CActiveDataProvider verwendest musst du eigentlich kein Pagination Objekt selbst erzeugen. Es wird vom DataProvider selbst angelegt. Also lass die Pagination im Controller mal weg und definiere sie in deiner search Methode:


public function actionBatchUpdate()

        {

            $model=new Noten('search');

            if(isset($_GET['Noten'])) {

                $model->unsetAttributes();

                $model->attributes = $_GET['Noten'];

            }


            $dataProvider =  $model->search();


            if(isset($_POST['Noten'])) {

                $valid=true;

                foreach($dataProvider->data as $i=>$item)

                {

                    if(isset($_POST['Noten'][$i]))

                        $item->attributes=$_POST['Noten'][$i]; 

                        $valid=$item->validate() && $valid;

                        $item->save();

                }

            }


            $this->render('batchUpdate', array(

                'model' => $model,

                'items' => $dataProvider->data,

                'pages' => $dataProvider->pagination,

            ));

        }

Und in der search Methode hängst du sie beim Erstellen des DataProviders in die Konfiguration:


$data = new CActiveDataProvider($this, array(

            'criteria'=>$criteria,

            'pagination' => array(

                'pageSize' => 6

            )

            'sort'=>array(

                'defaultOrder'=>'Zuname ASC',

                'attributes'=>array('Name_Komponist'=>array('asc'=>'Zuname','desc'=>'Zuname DESC'),

                                    'Name_Verlag'=>array('asc'=>'Name','desc'=>'Name DESC'),

                                    'Titel_Sammelheft'=>array('asc'=>'Titel','desc'=>'Titel DESC'),

                                    '*'),

        )

    ));

    return $data;

der "totalItemCount" wird vom DataProvider ohnehin von selbst anhand des CDbCriteria objects "errechnet" sobald getData() aufgerufen wird. Siehe: https://github.com/yiisoft/yii/blob/1.1.13/framework/web/CActiveDataProvider.php#L127-L131

lg,

Hannes

Ok, aber wie kann ich die Pagination dann im View anzeigen? So, wie ich es vorher gemacht hatte, geht es anscheinend nicht.

Hier der Code aus dem View:


<?php $form=$this->beginWidget('CActiveForm', array(

    'id'=>'noten-multiform',

    'enableAjaxValidation'=>false,

)); ?>


<table>

<tr>

<th>Komponist</th><th>Werk / Besetzung</th><th>Verlag / Nr</th><th>Reihe / Anmerkung</th><th>Sammelheft</th></tr>

<?php foreach($items as $i=>$item): ?>

    <tr>

        ...

        <td><?php echo $form->textField($item,"[$i]Werk"); ?><br>

            <?php echo $form->textField($item,"[$i]Besetzung"); ?></td>

        ...

    </tr>

<?php endforeach; ?>

</table>

<div class="row buttons">

        <?php echo CHtml::submitButton('Datensätze speichern'); ?>

</div>

<?php $this->endWidget(); ?>

</div>


<?php

 $this->widget('CLinkPager', array(

    'pages' => $pages,

)); 

Tut mir leid, ich stehe daneben. :(

Gruß

Ferdinand

Kein Problem, dafür is das Forum ja gedacht :)

Im View bleibt auch alles beim alten, die "pages" Variable ist auch immernoch vorhanden. Vorher hast du ein eigenes Pagination Object erzeugt, jetzt greifst du auf das Pagination Objekt des DataProviders zu. Dem View ist das im Prinzip "wurscht" solange in $pages ein CPagination Objekt liegt.




$this->render('batchUpdate', array(

                'model' => $model,

                'items' => $dataProvider->data,

                'pages' => $dataProvider->pagination, //das hier ist wichtig

            ));



Btw.: Hat es einen bestimmten Grund warum du keinen CListView verwendest? Der würde dir dieses ganze Getue mit der Paginierung eventuell abnehmen. Die ist nämlich ein bekannter Stolperstein wie man sieht :)

Danke für die detailierte Hilfe. Jetzt funktioniert auch die Pagination.

Ich wollte eine tabellarische Eingabe realisieren. CListView oder CGridView bieten ja keine Input Felder sondern nur eine Anzeige der Feldinhalte.

Ich habe ja verschiedenes probiert: Eine Extension Multimodelform, eine andere Extension EditableGridView und eben so, wie jetzt nach der Anleitung wie man eine tabellarische Eingabe machen kann (im Online Yii Handbuch).

Bei jeder dieser Varianten bin ich zunächst irgendwo hängen geblieben - aber letztere Variante funktioniert jetzt ::) Mit dem, was ich jetzt dazugelernt habe, sollte ich vielleicht auch mit anderen Varianten zum Erfolg kommen können.

Mich wundert, dass es so etwas, wie eine Dateneingabe in Stil einer Tabellenkalkulation nicht fix und fertig gibt in Yii. Oder habe ich es nicht gefunden? Alles, was ich gefunden habe (siehe oben), ist irgendwie nicht recht fertig oder schon ziemlich veraltet.

LG

Ferdinand

Freut mich, dass es geklappt hat :)

Was die Sache sicher schwer macht ist, dass man mit ListViews und GridViews in simplen Fällen ja leicht zum Ziel kommt, solche Themen wie deines dann aber schon echtes Know-How verlangen was DataProvider, Pagination, models und rules. Wenn man dann extensions verwendet und die auch nur in einem Detail nicht ganz passen ist man eigentlich völlig verloren. Ich persönlich verwende nur Extensions die ich auch selbst verstehe (und implementieren könnte). Bei allen anderen Dingen hilft leider nur ausprobieren. Aber eventuell kannst du ja einen Wiki Eintrag schreiben um anderen dein Leiden zu ersparen? :)

lg,

Hannes