logo
Ask your Symfony questions! Pay money and get answers fast! (more info)

Warning: Please do not give out any FTP or ssh credentials to anyone, unless you trust them completely. Giving out login details is dangerous.

If the asker does not get an answer then they have 10 days to request a refund.

$25
How do I embed forms when auto-generating admin modules?

This is Symfony 1.4, using Propel.

I have a site where the user admin is auto-generated with commands like this:

./symfony propel:generate-admin --theme="roboo" --module="deal" frontend Deal

But the "deal" table (and model) has a notification_id that links to the notification table. The relationship is one-to-one -- each deal gets one notification, and each notification is unique to a deal (though the notifications can actually be given out to many different models, including the tables for events, places, people, etc).

I want to embed the notification form in the deal form, so both are shown to the user as a single form. I know how to do this when I'm writing the action code myself, but in this case much of the code is auto-generated. Is there a way to do embedded forms with the generate-admin command?




This question has been answered.

Lawrence Krubner | 11/13/12 at 4:10pm Edit


(19) Responses

See a threaded view of answers?

Warning: Please do not give out any FTP or ssh credentials to anyone, unless you trust them completely. Giving out login details is dangerous.

  • avatar
    Last edited:
    11/13/12
    4:13pm
    Luis Cordova says:

    would be cool if we could filter questions for sf2 now, sf1.x is obsolete already and his period of support has or will end very soon

  • avatar
    Last edited:
    11/13/12
    4:18pm
    mullerivan says:

    attache a example
    add this in the DealForm



    // profile form?
    $profileFormClass = sfConfig::get('app_sf_guard_plugin_profile_class', 'sfGuardUserProfile') . 'Form';

    if (class_exists($profileFormClass))
    {
    $profileForm = new $profileFormClass();
    unset($profileForm[$this->getPrimaryKey()]);
    unset($profileForm[sfConfig::get('app_sf_guard_plugin_profile_field_name', 'user_id')]);

    $this->mergeForm($profileForm);
    }

    }

    public function updateObject($values = null)
    {
    parent::updateObject($values);

    // update defaults for profile
    if (!is_null($profile = $this->getProfile()))
    {
    $values = $this->getValues();
    unset($values[$this->getPrimaryKey()]);

    $profile->fromArray($values, BasePeer::TYPE_FIELDNAME);
    $profile->save();
    }


    return $this->object;

    }

    public function updateDefaultsFromObject()
    {
    parent::updateDefaultsFromObject();

    // update defaults for profile

    if (!is_null($profile = $this->getProfile()))
    {

    $values = $profile->toArray(BasePeer::TYPE_FIELDNAME);

    unset($values[$this->getPrimaryKey()]);

    // update defaults for the main object
    if ($this->isNew)
    {
    $this->setDefaults(array_merge($values, $this->getDefaults()));
    }
    else
    {
    $this->setDefaults(array_merge($this->getDefaults(), $values));
    }
    }

    }

    protected function getProfile()
    {
    try
    {
    return $this->object->getProfile();
    }
    catch (sfException $e)
    {
    // no profile
    return null;
    }

    }

    protected function getPrimaryKey()
    {
    if (!is_null($this->pkName))
    {
    return $this->pkName;
    }

    $profileClass = sfConfig::get('app_sf_guard_plugin_profile_class', 'sfGuardUserProfile');
    if (class_exists($profileClass))
    {
    $tableMap = call_user_func(array($profileClass . 'Peer', 'getTableMap'));
    foreach ($tableMap->getColumns() as $column)
    {
    if ($column->isPrimaryKey())
    {
    return $this->pkName = call_user_func(array($profileClass . 'Peer', 'translateFieldname'), $column->getPhpName(), BasePeer::TYPE_PHPNAME, BasePeer::TYPE_FIELDNAME);
    }
    }
    }

    }

    Previous versions of this answer: 11/13/12 at 4:18pm

  • avatar
    Last edited:
    11/13/12
    5:11pm
    Milena Dimitrova says:

    I don't think the generator can help you for this but it can be easily achieved by adding the following custom methods to your DealForm:

     public function configure()
    {

    unset(
    $this['notification_id']
    );

    $type = $this->getObject()->getNotification();
    $this->embedForm('notification', new NotificationForm($type));

    parent::configure();


    }



    protected function doSave($con = null)
    {
    if (null === $con)
    {
    $con = $this->getConnection();
    }

    $this->updateObject();

    //gets called first
    $this->saveEmbeddedForms($con);
    $this->getObject()->setNotification($this->getEmbeddedForm('notification')->getObject());

    // same object after saving the embedded form
    $this->getObject()->save($con);
    }



    Having this in your form while using the standard generator command and unmodified generator.yml (form: ~) should display the embedded Notification form as an inalienable part of the Deal form.

  • avatar
    Last edited:
    11/14/12
    12:23pm
    Lawrence Krubner says:

    Hmm, and how are these changes made visible? I changed the form class and then regenerated the module with:

    ./symfony propel:generate-admin --theme="roboo" --module="deal" frontend Deal

    but I don't see any changes in the forms.

  • avatar
    Last edited:
    11/14/12
    12:27pm
    Lawrence Krubner says:


    See screenshot. I cleared cache but I am still not seeing the embedded form. Not sure why.


    attachment image expert uploaded image

  • avatar
    Last edited:
    11/14/12
    12:29pm
    Lawrence Krubner says:

    My DealForm class now consists of:

    class DealForm extends BaseDealForm
    {
    public function configure()
    {

    //setup the form with the parent method
    //parent::setUp();

    $this->setWidget('image', new sfWidgetFormInputFileEditable(array(
    'file_src' => $this->getObject()->getImage()?'/uploads/'.$this->getObject()->getImage():'',
    'edit_mode' => $this->getObject()->getImage()?true:false,
    'template' => '%file%<br />%input%',
    'is_image' => true)));

    $this->setValidator('image', new sfValidatorFile(array('path' => sfConfig::get('sf_upload_dir'), 'required' => false)));

    $this->setWidget('app_id', new sfWidgetFormInputHidden());
    $this->setDefault('app_id', 1);

    $this->setWidget('starts_at', new sfWidgetFormJQueryDate());
    $this->setWidget('ends_at', new sfWidgetFormJQueryDate());

    unset(
    $this['notification_id']
    );

    $type = $this->getObject()->getNotification();
    $this->embedForm('notification', new NotificationForm($type));
    parent::configure();
    }


    public function updateObject($values = null)
    {
    $object = parent::updateObject($values);

    $notification = new Notification();
    $notification->setMessage($object->getDescription());
    // default will be to notify at 40 hours before event starts
    /*$notification->setPushAt(date('Y-m-d H:i:s', time() - 144000); */
    $notification->setAppId($object->getAppId());

    return $object;
    }





    protected function doSave($con = null)
    {
    if (null === $con)
    {
    $con = $this->getConnection();
    }
    $this->updateObject();

    //gets called first
    $this->saveEmbeddedForms($con);
    $this->getObject()->setNotification($this->getEmbeddedForm('notification')->getObject());

    // same object after saving the embedded form
    $this->getObject()->save($con);
    }
    }



    but it occurs to me, the form class is not really used when auto-generating an admin form, right? I really need to edit the filter form class?

    Maybe these changes should go in:

    lib/filter/DealFormFilter.class.php

    ???

    Is the syntax the same? I guess I'll give it a try.

  • avatar
    Last edited:
    11/14/12
    12:30pm
    Lawrence Krubner says:

    I agree. I am frustrated with the extent of the changes in Symfony 2.0. I have lost some of my love for Symfony, due to the backwards-breaking changes in 2.0.

  • avatar
    Last edited:
    11/14/12
    12:30pm
    Lawrence Krubner says:

    Okay, I will try this now.

  • avatar
    Last edited:
    11/14/12
    12:41pm
    Lawrence Krubner says:

    Hmm, should I used embedRelation() rather than embedForm()?

  • avatar
    Last edited:
    11/14/12
    12:45pm
    Lawrence Krubner says:

    I am trying to translate this to my own situation. In your example, the profile object is sort of the same as my Deal object? And what is the equivalent of my Notification model?

  • avatar
    Last edited:
    11/14/12
    12:57pm
    Lawrence Krubner says:

    I tried using embedRelation but got this error:

    [Wed Nov 14 18:06:27 2012] [error] [client 67.247.7.85] embedRelation() only works for one-to-many relationships


    But in the schema Deal has a notification_id that is a foreign key pointing to Notification. In other words, this is a one-to-many relationship. So I am confused why this complains. My schema.yml (in part):

      deal:
    id:
    app_id: { type: integer, required: true, foreignTable: app, foreignReference: id, onDelete: cascade }
    title: varchar(255)
    deal: varchar(255)
    original_price: integer
    discounted_price: integer
    starts_at: timestamp
    ends_at: timestamp
    place_id: { type: integer, required: false, foreignTable: place, foreignReference: id, onDelete: cascade }
    limited_to: integer
    is_recurring: boolean
    push_before_sec: integer
    push_text: longvarchar
    image: varchar(255)
    description: longvarchar
    notification_id: { type: integer, required: false, foreignTable: notification, foreignReference: id, onDelete: cascade }



    notification:
    id:
    app_id: { type: integer, required: true, foreignTable: app, foreignReference: id, onDelete: cascade }
    push_at: timestamp
    message: longvarchar
    prior_birthday_sec: integer


  • avatar
    Last edited:
    11/14/12
    1:06pm
    Lawrence Krubner says:

    Hmm, wait, maybe I have the relattionship backwards? Maybe I need to use mergeRelation(), in sfPropelForm?

  • avatar
    Last edited:
    11/14/12
    3:23pm
    Milena Dimitrova says:

    For changes to become visible you do not have to re-generate the admin module or to edit the filter form class and embedRelation() is not needed either. embedForm() should work provided that your base generated classes are relational.

    Question: before adding this

    unset(
    $this['notification_id']
    );

    to you form class - were you able to see a dropdown field with label 'Notification' in the autogenerated Deal form?

    Also, please send the content of your generator.yml

  • avatar
    Last edited:
    11/14/12
    4:05pm
    Lawrence Krubner says:

    Sadly, no, I do not see Notification. If I comment out unset() and clear the cache, so my code is:

      public function configure()
    {

    //setup the form with the parent method
    //parent::setUp();

    $this->setWidget('image', new sfWidgetFormInputFileEditable(array(
    'file_src' => $this->getObject()->getImage()?'/uploads/'.$this->getObject()->getImage():'',
    'edit_mode' => $this->getObject()->getImage()?true:false,
    'template' => '%file%<br />%input%',
    'is_image' => true)));

    $this->setValidator('image', new sfValidatorFile(array('path' => sfConfig::get('sf_upload_dir'), 'required' => false)));

    $this->setWidget('app_id', new sfWidgetFormInputHidden());
    $this->setDefault('app_id', 1);

    $this->setWidget('starts_at', new sfWidgetFormJQueryDate());
    $this->setWidget('ends_at', new sfWidgetFormJQueryDate());

    // unset(
    // $this['notification_id']
    // );

    $type = $this->getObject()->getNotification();
    $this->embedForm('notification', new NotificationForm($type));
    parent::configure();
    }



    Then I still see nothing about notifications in the form.

  • avatar
    Last edited:
    11/14/12
    4:21pm
    Lawrence Krubner says:

    The generator for the deals form looks like this:


    generator:
    class: sfPropelGenerator
    param:
    model_class: Deal
    theme: roboo
    non_verbose_templates: true
    with_show: false
    singular: Deal
    plural: Deals
    route_prefix: deal
    with_propel_route: 1
    actions_base_class: sfActions

    config:
    module_id: 7
    nav:
    index: { title: Deals }
    new: { title: Add Deals }
    actions: ~
    fields:
    limited_to:
    attributes:
    size: 5
    help: 0 for no limit
    title:
    attributes:
    size: 30
    original_price:
    attributes:
    size: 5
    help: US Dollars
    discounted_price:
    attributes:
    size: 5
    help: US Dollars
    starts_at:
    date_format: MM/dd/yyyy
    ends_at:
    label: Expires
    date_format: MM/dd/yyyy
    list:
    display: [=title, starts_at, ends_at]
    batch_actions: []
    filter: ~
    form:
    class: DealForm
    edit:
    display: [title, original_price, discounted_price, starts_at, ends_at, limited_to, description, image, place_id]
    new:
    display: [title, original_price, discounted_price, starts_at, ends_at, limited_to, description, image, place_id]



  • avatar
    Last edited:
    11/14/12
    4:28pm
    Milena Dimitrova says:

    In general if you remove all code from the configure() like this:

    function configure()
    {
    }



    you should be able to see ALL form fields corresponding to all table fields (except for the ID field, of course, which should be hidden)

    This is relying on the assumption that you are not explicitly specifying in generator.yml (or in some template) which form fields you want to have.

    So, for example if your generator.yml file looks like this you will have strong>ALL fields displayed including the dropdown for Notification (if you don't embed the form).


    generator:
    class: sfPropelGenerator
    param:
    model_class: deal
    theme: roboo
    non_verbose_templates: true
    with_show: false
    singular: deal
    plural: deals
    route_prefix: deal
    with_propel_route: 1
    actions_base_class: sfActions

    config:
    actions: ~
    fields: ~
    list: ~
    filter: ~
    form: ~
    edit: ~
    new: ~


    If you still do not see the notification fields:
    - generate models, forms and filters again
    - make sure there are no templates in the autogenerated module and your generator.yml is clean
    - make sure you use a non-cached environment.

  • avatar
    Last edited:
    11/14/12
    4:40pm
    Milena Dimitrova says:

    Thank you for sharing your generator.yml file content. So, it seems like you do not show all fields because you restrict your 'edit' and 'new' actions by specifying exactly which fields to display:

     edit:

    display: [title, original_price, discounted_price, starts_at, ends_at, limited_to, description, image, place_id]

    new:

    display: [title, original_price, discounted_price, starts_at, ends_at, limited_to, description, image, place_id]



    If you want to keep it this way you have to also add the new 'notification' element which stands for
    $this->embedForm('notification', new NotificationForm($type));


    So change in generator.yml to:

    edit:

    display: [title, original_price, discounted_price, starts_at, ends_at, limited_to, description, image, place_id, notification]

    new:

    display: [title, original_price, discounted_price, starts_at, ends_at, limited_to, description, image, place_id, notification]

  • avatar
    Last edited:
    11/14/12
    4:42pm
    Lawrence Krubner says:

    Ah, good tip, that worked. See screenshot. It finally appears.

    One last thing, I need for the app_id to disappear from the form (I would set that value elsewhere, I think in the form -- the notification should have the same app_id as the deal). Do you know how I can make it disappear from the form? Do I unset() from the Notification module?

    attachment image expert uploaded image

  • avatar
    Last edited:
    11/14/12
    4:49pm
    Milena Dimitrova says:

    You can unset the app_id field from the DealForm class configure() method as well:

    unset(
    $this['notification_id'],
    $this['app_id ']
    );

    Alternatively you can make it a hidden field

    Anyway, as it is a required field, you will have to set the app_id value before the object is (first) saved.

This question has expired.



Lawrence Krubner voted on this question.



Current status of this question: Completed



Warning: Please do not give out any FTP or ssh credentials to anyone, unless you trust them completely. Giving out login details is dangerous.

If the asker does not get an answer then they have 10 days to request a refund.