瀏覽代碼

add View mode

Thomas Rabaix 14 年之前
父節點
當前提交
58fa7ef201

+ 189 - 6
Admin/Admin.php

@@ -22,9 +22,12 @@ use Sonata\AdminBundle\Datagrid\ListMapper;
 use Sonata\AdminBundle\Datagrid\DatagridMapper;
 
 use Sonata\AdminBundle\Admin\Pool;
+
 use Sonata\AdminBundle\Builder\FormContractorInterface;
 use Sonata\AdminBundle\Builder\ListBuilderInterface;
 use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
+use Sonata\AdminBundle\Builder\ViewBuilderInterface;
+
 use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
 use Sonata\AdminBundle\Route\RouteCollection;
 use Sonata\AdminBundle\Model\ModelManagerInterface;
@@ -50,12 +53,27 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
 
     /**
      * The list FieldDescription constructed from the $list property
-     * and the the configureListField method
+     * and the configureListField method
      *
      * @var array
      */
     protected $listFieldDescriptions = array();
 
+    /**
+     * The view field definitions (quick property definition)
+     *
+     * @var array
+     */
+    protected $view = array();
+
+    /**
+     * The view FieldDescription constructed from the $view property
+     * and the configureListField method
+     *
+     * @var array
+     */
+    protected $viewFieldDescriptions = array();
+
     /**
      * The form field definition (quick property definition)
      *
@@ -121,6 +139,13 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
      */
     protected $formGroups = false;
 
+    /**
+     * The view group disposition
+     *
+     * @var array|boolean
+     */
+    protected $viewGroups = false;
+
     /**
      * The label class name  (used in the title/breadcrumb ...)
      *
@@ -267,6 +292,13 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
      */
     protected $listBuilder;
 
+    /**
+     * The related view builder
+     *
+     * @var \Sonata\AdminBundle\View\ViewBuilderInterface
+     */
+    protected $viewBuilder;
+
     /**
      * The related datagrid builder
      *
@@ -311,6 +343,8 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
         'form_groups'   => false,
         'list_fields'   => false,
         'filter_fields' => false,
+        'view_fields'   => false,
+        'view_groups'   => false,
         'routes'        => false,
         'side_menu'     => false,
     );
@@ -344,6 +378,15 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
 
     }
 
+    /**
+     *
+     * @param DatagridMapper
+     */
+    protected function configureViewFields(DatagridMapper $filter)
+    {
+
+    }
+
     /**
      * configure the Admin routes
      *
@@ -433,6 +476,29 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
 
     }
 
+    /**
+     * build the view FieldDescription array
+     *
+     * @return void
+     */
+    protected function buildViewFieldDescriptions()
+    {
+        if ($this->loaded['view_fields']) {
+            return;
+        }
+
+        $this->loaded['view_fields'] = true;
+
+        $this->viewFieldDescriptions = $this->getBaseFields($this->view);
+
+        // normalize field
+        foreach ($this->viewFieldDescriptions as $fieldDescription) {
+            $this->getViewBuilder()->fixFieldDescription($this, $fieldDescription);
+        }
+
+        return $this->viewFieldDescriptions;
+    }
+
     /**
      * build the list FieldDescription array
      *
@@ -744,6 +810,7 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
         $collection->add('batch');
         $collection->add('edit', $this->getRouterIdParameter().'/edit');
         $collection->add('delete', $this->getRouterIdParameter().'/delete');
+        $collection->add('view', $this->getRouterIdParameter().'/view');
 
         // add children urls
         foreach ($this->getChildren() as $children) {
@@ -854,6 +921,16 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
         return 'SonataAdminBundle:CRUD:edit.html.twig';
     }
 
+    /**
+     * Returns the view template
+     *
+     * @return string the view template
+     */
+    public function getViewTemplate()
+    {
+        return 'SonataAdminBundle:CRUD:view.html.twig';
+    }
+
     /**
      * Returns an instance of the related classname
      *
@@ -968,6 +1045,33 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
         }
     }
 
+      /**
+     * build the view group array
+     *
+     * @return void
+     */
+    public function buildViewGroups()
+    {
+        if ($this->loaded['view_groups']) {
+            return;
+        }
+
+        $this->loaded['view_groups'] = true;
+
+        if (!$this->viewGroups) {
+            $this->viewGroups = array(
+                false => array('fields' => array_keys($this->getViewFieldDescriptions()))
+            );
+        }
+
+        // normalize array
+        foreach ($this->viewGroups as $name => $group) {
+            if (!isset($this->viewGroups[$name]['collapsed'])) {
+                $this->viewGroups[$name]['collapsed'] = false;
+            }
+        }
+    }
+
     /**
      * Returns a form depend on the given $object
      *
@@ -1131,11 +1235,6 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
         return $this->maxPerPage;
     }
 
-    public function setFormGroups($formGroups)
-    {
-        $this->formGroups = $formGroups;
-    }
-
     public function getFormGroups()
     {
         $this->buildFormGroups();
@@ -1143,6 +1242,13 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
         return $this->formGroups;
     }
 
+    public function getViewGroups()
+    {
+        $this->buildViewGroups();
+
+        return $this->viewGroups;
+    }
+
     /**
      * set the parent FieldDescription
      *
@@ -1266,6 +1372,65 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
         unset($this->formFieldDescriptions[$name]);
     }
 
+    /**
+     * build and return the collection of form FieldDescription
+     *
+     * @return array collection of form FieldDescription
+     */
+    public function getViewFieldDescriptions()
+    {
+        $this->buildViewFieldDescriptions();
+
+        return $this->viewFieldDescriptions;
+    }
+
+    /**
+     * Returns the form FieldDescription with the given $name
+     *
+     * @param string $name
+     * @return \Sonata\AdminBundle\Admin\FieldDescriptionInterface
+     */
+    public function getViewFieldDescription($name)
+    {
+        return $this->hasViewFieldDescription($name) ? $this->viewFieldDescriptions[$name] : null;
+    }
+
+    /**
+     * Returns true if the admin has a FieldDescription with the given $name
+     *
+     * @param string $name
+     * @return bool
+     */
+    public function hasViewFieldDescription($name)
+    {
+        $this->buildViewFieldDescriptions();
+
+        return array_key_exists($name, $this->viewFieldDescriptions);
+    }
+
+    /**
+     * add a FieldDescription
+     *
+     * @param string $name
+     * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
+     * @return void
+     */
+    public function addViewFieldDescription($name, FieldDescriptionInterface $fieldDescription)
+    {
+        $this->viewFieldDescriptions[$name] = $fieldDescription;
+    }
+
+    /**
+     * remove a FieldDescription
+     *
+     * @param string $name
+     * @return void
+     */
+    public function removeViewFieldDescription($name)
+    {
+        unset($this->viewFieldDescriptions[$name]);
+    }
+
     /**
      * Returns the collection of list FieldDescriptions
      *
@@ -1770,6 +1935,23 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
         return $this->listBuilder;
     }
 
+    /**
+     * @param \Sonata\AdminBundle\Builder\ViewBuilderInterface $viewBuilder
+     * @return void
+     */
+    public function setViewBuilder(ViewBuilderInterface $viewBuilder)
+    {
+        $this->viewBuilder = $viewBuilder;
+    }
+
+    /**
+     * @return \Sonata\AdminBundle\Builder\ViewBuilderInterface
+     */
+    public function getViewBuilder()
+    {
+        return $this->viewBuilder;
+    }
+
     /**
      * @param Pool $configurationPool
      * @return void
@@ -1854,6 +2036,7 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
             'EDIT'      => array('EDIT'),
             'LIST'      => array('LIST'),
             'CREATE'    => array('CREATE'),
+            'VIEW'      => array('VIEW'),
             'DELETE'    => array('DELETE'),
             'OPERATOR'  => array('OPERATOR')
         );

+ 105 - 0
Builder/ORM/ViewBuilder.php

@@ -0,0 +1,105 @@
+<?php
+
+/*
+ * This file is part of the Sonata package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Builder\ORM;
+
+use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
+use Sonata\AdminBundle\Model\ModelManagerInterface;
+use Sonata\AdminBundle\Admin\AdminInterface;
+use Sonata\AdminBundle\Admin\FieldDescriptionCollection;
+use Sonata\AdminBundle\Builder\ViewBuilderInterface;
+
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+
+class ViewBuilder implements ViewBuilderInterface
+{
+    public function getBaseList(array $options = array())
+    {
+        return new FieldDescriptionCollection;
+    }
+
+    public function addField(FieldDescriptionCollection $list, FieldDescriptionInterface $fieldDescription)
+    {
+        return $list->add($fieldDescription);
+    }
+
+    /**
+     * The method defines the correct default settings for the provided FieldDescription
+     *
+     * @param \Sonata\AdminBundle\Admin\AdminInterface $admin
+     * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
+     * @param array $options
+     * @return void
+     */
+    public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription, array $options = array())
+    {
+        $fieldDescription->mergeOptions($options);
+        $fieldDescription->setAdmin($admin);
+
+        if ($admin->getModelManager()->hasMetadata($admin->getClass())) {
+            $metadata = $admin->getModelManager()->getMetadata($admin->getClass());
+
+            // set the default field mapping
+            if (isset($metadata->fieldMappings[$fieldDescription->getName()])) {
+                $fieldDescription->setFieldMapping($metadata->fieldMappings[$fieldDescription->getName()]);
+            }
+
+            // set the default association mapping
+            if (isset($metadata->associationMappings[$fieldDescription->getName()])) {
+                $fieldDescription->setAssociationMapping($metadata->associationMappings[$fieldDescription->getName()]);
+            }
+        }
+
+        if (!$fieldDescription->getType()) {
+            throw new \RuntimeException(sprintf('Please define a type for field `%s` in `%s`', $fieldDescription->getName(), get_class($admin)));
+        }
+
+        $fieldDescription->setOption('code', $fieldDescription->getOption('code', $fieldDescription->getName()));
+        $fieldDescription->setOption('label', $fieldDescription->getOption('label', $fieldDescription->getName()));
+
+        if (!$fieldDescription->getTemplate()) {
+
+            $fieldDescription->setTemplate(sprintf('SonataAdminBundle:CRUD:view_%s.html.twig', $fieldDescription->getType()));
+
+            if ($fieldDescription->getType() == ClassMetadataInfo::MANY_TO_ONE) {
+                $fieldDescription->setTemplate('SonataAdminBundle:CRUD:view_orm_many_to_one.html.twig');
+            }
+
+            if ($fieldDescription->getType() == ClassMetadataInfo::ONE_TO_ONE) {
+                $fieldDescription->setTemplate('SonataAdminBundle:CRUD:view_orm_one_to_one.html.twig');
+            }
+
+            if ($fieldDescription->getType() == ClassMetadataInfo::ONE_TO_MANY) {
+                $fieldDescription->setTemplate('SonataAdminBundle:CRUD:view_orm_one_to_many.html.twig');
+            }
+
+            if ($fieldDescription->getType() == ClassMetadataInfo::MANY_TO_MANY) {
+                $fieldDescription->setTemplate('SonataAdminBundle:CRUD:view_orm_many_to_many.html.twig');
+            }
+        }
+
+        if ($fieldDescription->getType() == ClassMetadataInfo::MANY_TO_ONE) {
+            $admin->attachAdminClass($fieldDescription);
+        }
+
+        if ($fieldDescription->getType() == ClassMetadataInfo::ONE_TO_ONE) {
+            $admin->attachAdminClass($fieldDescription);
+        }
+
+        if ($fieldDescription->getType() == ClassMetadataInfo::ONE_TO_MANY) {
+            $admin->attachAdminClass($fieldDescription);
+        }
+
+        if ($fieldDescription->getType() == ClassMetadataInfo::MANY_TO_MANY) {
+            $admin->attachAdminClass($fieldDescription);
+        }
+    }
+}

+ 44 - 0
Builder/ViewBuilderInterface.php

@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the Sonata package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Builder;
+
+use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
+use Sonata\AdminBundle\Model\ModelManagerInterface;
+use Sonata\AdminBundle\Admin\AdminInterface;
+use Sonata\AdminBundle\Admin\FieldDescriptionCollection;
+
+interface ViewBuilderInterface
+{
+    /**
+     * @abstract
+     * @param array $options
+     * @return void
+     */
+    function getBaseList(array $options = array());
+
+    /**
+     * @abstract
+     * @param \Sonata\AdminBundle\Admin\FieldDescriptionCollection $list
+     * @param \Sonata\AdminBundle\Admin\FieldDescription $fieldDescription
+     * @return void
+     */
+    function addField(FieldDescriptionCollection $list, FieldDescriptionInterface $fieldDescription);
+
+    /**
+     * @abstract
+     * @param \Sonata\AdminBundle\Admin\AdminInterface $admin
+     * @param \Sonata\AdminBundle\Admin\FieldDescription $fieldDescription
+     * @param array $options
+     * @return void
+     */
+    function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription, array $options = array());
+}

+ 27 - 0
Controller/CRUDController.php

@@ -310,4 +310,31 @@ class CRUDController extends Controller
             'base_template' => $this->getBaseTemplate(),
         ));
     }
+
+    /**
+     * return the Response object associated to the view action
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     */
+    public function viewAction($id)
+    {
+        if (false === $this->admin->isGranted('VIEW')) {
+            throw new AccessDeniedException();
+        }
+
+        $object = $this->admin->getObject($this->get('request')->get($this->admin->getIdParameter()));
+
+        if (!$object) {
+            throw new NotFoundHttpException(sprintf('unable to find the object with id : %s', $id));
+        }
+
+        $this->admin->setSubject($object);
+
+        return $this->render($this->admin->getViewTemplate(), array(
+            'action'         => 'view',
+            'object'         => $object,
+            'admin'          => $this->admin,
+            'base_template'  => $this->getBaseTemplate(),
+        ));
+    }
 }

+ 0 - 1
Datagrid/ProxyQueryInterface.php

@@ -16,7 +16,6 @@ namespace Sonata\AdminBundle\Datagrid;
  */
 interface ProxyQueryInterface
 {
-
     function execute(array $params = array(), $hydrationMode = null);
 
     function __call($name, $args);

+ 4 - 0
DependencyInjection/Compiler/AddDependencyCallsPass.php

@@ -103,6 +103,10 @@ class AddDependencyCallsPass implements CompilerPassInterface
             $definition->addMethodCall('setFormContractor', array(new Reference(sprintf('sonata.admin.builder.%s_form', $manager_type))));
         }
 
+        if (!$definition->hasMethodCall('setViewBuilder')) {
+            $definition->addMethodCall('setViewBuilder', array(new Reference(sprintf('sonata.admin.builder.%s_view', $manager_type))));
+        }
+
         if (!$definition->hasMethodCall('setListBuilder')) {
             $definition->addMethodCall('setListBuilder', array(new Reference(sprintf('sonata.admin.builder.%s_list', $manager_type))));
         }

+ 2 - 0
Resources/config/doctrine_orm.xml

@@ -16,6 +16,8 @@
 
         <service id="sonata.admin.builder.orm_list" class="Sonata\AdminBundle\Builder\ORM\ListBuilder" />
 
+        <service id="sonata.admin.builder.orm_view" class="Sonata\AdminBundle\Builder\ORM\ViewBuilder" />
+
         <service id="sonata.admin.builder.orm_datagrid" class="Sonata\AdminBundle\Builder\ORM\DatagridBuilder">
             <argument type="service" id="form.factory" />
         </service>

+ 8 - 0
Resources/translations/SonataAdminBundle.en.xliff

@@ -42,6 +42,14 @@
                 <source>link_action_list</source>
                 <target>Return to list</target>
             </trans-unit>
+            <trans-unit id="link_action_view">
+                <source>link_action_view</source>
+                <target>View</target>
+            </trans-unit>
+            <trans-unit id="link_action_edit">
+                <source>link_action_edit</source>
+                <target>Edit</target>
+            </trans-unit>
             <trans-unit id="link_add">
                 <source>link_add</source>
                 <target>Add new</target>

+ 8 - 0
Resources/translations/SonataAdminBundle.fr.xliff

@@ -38,6 +38,14 @@
                 <source>link_action_create</source>
                 <target>Ajouter</target>
             </trans-unit>
+            <trans-unit id="link_action_view">
+                <source>link_action_view</source>
+                <target>Afficher</target>
+            </trans-unit>
+            <trans-unit id="link_action_edit">
+                <source>link_action_edit</source>
+                <target>Editer</target>
+            </trans-unit>
             <trans-unit id="link_action_list">
                 <source>link_action_list</source>
                 <target>Retourner à la liste</target>

+ 3 - 0
Resources/views/CRUD/base_edit.html.twig

@@ -14,6 +14,9 @@ file that was distributed with this source code.
 {% block actions %}
     <div class="sonata-actions">
         <ul>
+            {% if admin.isGranted('VIEW')%}
+                <li class="sonata-action-element"><a href="{{ admin.generateUrl('view', {'id' : object.id}) }}">{% trans from 'SonataAdminBundle' %}link_action_view{% endtrans %}</a></li>
+            {% endif %}
             {% if admin.isGranted('CREATE')%}
                 <li class="sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
             {% endif %}

+ 9 - 2
Resources/views/CRUD/base_list_field.html.twig

@@ -10,10 +10,17 @@ file that was distributed with this source code.
 #}
 
 <td class="sonata-ba-list-field sonata-ba-list-field-{{ field_description.type }}" objectId="{{ object.id }}">
-    {% if field_description.options.identifier is defined and admin.isGranted('EDIT') %}
-        <a href="{{ admin.generateUrl('edit', {'id': object.id}) }}">
+    {% if field_description.options.identifier is defined and admin.isGranted(['EDIT', 'VIEW']) %}
+
+        {% if admin.isGranted('EDIT') %}
+            <a href="{{ admin.generateUrl('edit', {'id': object.id}) }}">
+        {% else %}
+            <a href="{{ admin.generateUrl('view', {'id': object.id}) }}">
+        {% endif %}
+
             {% block field %}{{ value }}{% endblock %}
         </a>
+
     {% else %}
         {{ block('field') }}
     {% endif %}

+ 53 - 0
Resources/views/CRUD/base_view.html.twig

@@ -0,0 +1,53 @@
+{#
+
+This file is part of the Sonata package.
+
+(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+
+For the full copyright and license information, please view the LICENSE
+file that was distributed with this source code.
+
+#}
+
+{% extends base_template %}
+
+{% block actions %}
+    <div class="sonata-actions">
+        <ul>
+            {% if admin.isGranted('EDIT')%}
+                <li class="sonata-action-element"><a href="{{ admin.generateUrl('edit', {'id' : object.id}) }}">{% trans from 'SonataAdminBundle' %}link_action_edit{% endtrans %}</a></li>
+            {% endif %}
+
+            {% if admin.isGranted('CREATE')%}
+                <li class="sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
+            {% endif %}
+        </ul>
+    </div>
+{% endblock %}
+
+{% block side_menu %}{{ admin.sidemenu(action).render|raw }}{% endblock %}
+
+{% block view %}
+    <div class="sonata-ba-view">
+        {% for name, view_group in admin.viewgroups %}
+            <table>
+                {% if name %}
+                    <tr class="sonata-ba-view-title">
+                        <td colspan="2">
+                            {{ name|trans({}, admin.translationdomain) }}
+                        </td>
+                    </tr>
+                {% endif %}
+
+                {% for field_name in view_group.fields %}
+                    <tr class="sonata-ba-view-container">
+                        {% if admin.viewfielddescriptions[field_name] is defined %}
+                            {{ admin.viewfielddescriptions[field_name]|render_view_element(object) }}
+                        {% endif %}
+                    </tr>
+                {% endfor %}
+            </table>
+        {% endfor %}
+    </div>
+{% endblock %}
+

+ 16 - 0
Resources/views/CRUD/base_view_field.html.twig

@@ -0,0 +1,16 @@
+{#
+
+This file is part of the Sonata package.
+
+(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+
+For the full copyright and license information, please view the LICENSE
+file that was distributed with this source code.
+
+#}
+
+
+<tr>
+    <td>{% block name %}{{ field_description.name }}{% endblock %}</td>
+    <td>{% block value %}{{ value }}{% endblock %}</td>
+</tr>

+ 13 - 0
Resources/views/CRUD/view.html.twig

@@ -0,0 +1,13 @@
+{#
+
+This file is part of the Sonata package.
+
+(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+
+For the full copyright and license information, please view the LICENSE
+file that was distributed with this source code.
+
+#}
+
+{% extends 'SonataAdminBundle:CRUD:base_view.html.twig' %}
+

+ 5 - 0
Resources/views/standard_layout.html.twig

@@ -55,6 +55,7 @@ file that was distributed with this source code.
         {# initialize block value #}
         {% set preview      = block('preview') %}
         {% set form         = block('form') %}
+        {% set view         = block('view') %}
         {% set list_table   = block('list_table') %}
         {% set list_filters = block('list_filters') %}
         {% set side_menu    = block('side_menu') %}
@@ -112,6 +113,10 @@ file that was distributed with this source code.
                         <div class="sonata-ba-content">{{ content|raw }}</div>
                     {% endif %}
 
+                    {% if view is not empty %}
+                        <div class="sonata-ba-view">{{ view|raw }}</div>
+                    {% endif %}
+
                     {% if form is not empty %}
                         <div class="sonata-ba-form">{{ form|raw }}</div>
                     {% endif %}

+ 104 - 0
Show/ShowMapper.php

@@ -0,0 +1,104 @@
+<?php
+/*
+ * This file is part of the Sonata package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+namespace Sonata\AdminBundle\Datagrid;
+
+use Sonata\AdminBundle\Admin\AdminInterface;
+use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
+use Sonata\AdminBundle\Model\ModelManagerInterface;
+use Sonata\AdminBundle\Admin\FieldDescriptionCollection;
+use Sonata\AdminBundle\Builder\ShowBuilderInterface;
+
+/**
+ * This class is used to simulate the Form API
+ *
+ */
+class ShowMapper
+{
+    protected $showBuilder;
+
+    protected $list;
+
+    protected $admin;
+
+    public function __construct(ShowBuilderInterface $showBuilder, FieldDescriptionCollection $list, AdminInterface $admin)
+    {
+        $this->showBuilder  = $showBuilder;
+        $this->list         = $list;
+        $this->admin        = $admin;
+    }
+
+    /**
+     * @throws \RuntimeException
+     * @param string $name
+     * @param array $fieldDescriptionOptions
+     * @return \Sonata\AdminBundle\Datagrid\ListMapper
+     */
+    public function add($name, array $fieldDescriptionOptions = array())
+    {
+        if ($name instanceof FieldDescriptionInterface) {
+
+            $fieldDescription = $name;
+            $fieldDescription->mergeOptions($fieldDescriptionOptions);
+
+        } else if (is_string($name) && !$this->admin->hasListFieldDescription($name)) {
+
+            $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance(
+                $this->admin->getClass(),
+                $name,
+                $fieldDescriptionOptions
+            );
+
+            $this->showBuilder->fixFieldDescription($this->admin, $fieldDescription, $fieldDescriptionOptions);
+            $this->admin->addListFieldDescription($name, $fieldDescription);
+
+        } else if (is_string($name) && $this->admin->hasShowFieldDescription($name)) {
+            $fieldDescription = $this->admin->getShowFieldDescription($name);
+        } else {
+            throw new \RuntimeException('invalid state');
+        }
+
+        // add the field with the FormBuilder
+        $this->showBuilder->addField(
+            $this->list,
+            $fieldDescription
+        );
+
+        return $this;
+    }
+
+    /**
+     * @param string $name
+     * @return array
+     */
+    public function get($name)
+    {
+        return $this->list->get($name);
+    }
+
+    /**
+     * @param string $key
+     * @return bool
+     */
+    public function has($key)
+    {
+        return $this->list->has($key);
+    }
+
+    /**
+     * @param  $key
+     * @return void
+     */
+    public function remove($key)
+    {
+        $this->admin->removeShowFieldDescription($key);
+        $this->list->remove($key);
+    }
+}

+ 59 - 17
Twig/Extension/SonataAdminExtension.php

@@ -38,9 +38,10 @@ class SonataAdminExtension extends \Twig_Extension
     public function getFilters()
     {
         return array(
-            'render_list_element'         => new \Twig_Filter_Method($this, 'renderListElement', array('is_safe' => array('html'))),
-            'render_form_element'         => new \Twig_Filter_Method($this, 'renderFormElement', array('is_safe' => array('html'))),
-            'render_filter_element'       => new \Twig_Filter_Method($this, 'renderFilterElement', array('is_safe' => array('html'))),
+            'render_list_element'    => new \Twig_Filter_Method($this, 'renderListElement', array('is_safe' => array('html'))),
+            'render_form_element'    => new \Twig_Filter_Method($this, 'renderFormElement', array('is_safe' => array('html'))),
+            'render_filter_element'  => new \Twig_Filter_Method($this, 'renderFilterElement', array('is_safe' => array('html'))),
+            'render_view_element'    => new \Twig_Filter_Method($this, 'renderViewElement', array('is_safe' => array('html'))),
         );
     }
 
@@ -59,6 +60,23 @@ class SonataAdminExtension extends \Twig_Extension
         return 'sonata_admin';
     }
 
+    /**
+     * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
+     * @param string $default
+     * @return \Twig_TemplateInterface
+     */
+    protected function getTemplate(FieldDescriptionInterface $fieldDescription, $default)
+    {
+        // todo: find a better solution
+        try {
+            $template = $this->environment->loadTemplate($fieldDescription->getTemplate());
+        } catch(\Twig_Error_Loader $e) {
+            $template = $this->environment->loadTemplate($default);
+        }
+
+        return $template;
+    }
+
     /**
      * render a list element from the FieldDescription
      *
@@ -69,23 +87,30 @@ class SonataAdminExtension extends \Twig_Extension
      */
     public function renderListElement($object, FieldDescriptionInterface $fieldDescription, $params = array())
     {
-        $template = $this->environment->loadTemplate($fieldDescription->getTemplate());
+        $template = $this->getTemplate($fieldDescription, 'SonataAdminBundle:CRUD:base_list.html.twig');
 
-        return $this->output($fieldDescription, $template->render(array_merge($params, array(
+        return $this->output($fieldDescription, $template, array_merge($params, array(
             'admin'  => $fieldDescription->getAdmin(),
             'object' => $object,
             'value'  => $this->getValueFromFieldDescription($object, $fieldDescription),
             'field_description' => $fieldDescription
-        ))));
+        )));
     }
 
-
-    public function output(FieldDescriptionInterface $fieldDescription, $content)
+    /**
+     * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
+     * @param string $content
+     * @return string
+     */
+    public function output(FieldDescriptionInterface $fieldDescription, \Twig_TemplateInterface $template, array $parameters = array())
     {
+        $content = $template->render($parameters);
+
         if ($this->environment->isDebug()) {
-            return sprintf("\n<!-- START - fieldName: %s, template: %s -->\n%s\n<!-- END - fieldName: %s -->",
+            return sprintf("\n<!-- START  \n  fieldName: %s\n  template: %s\n  compiled template: %s\n -->\n%s\n<!-- END - fieldName: %s -->",
                 $fieldDescription->getFieldName(),
                 $fieldDescription->getTemplate(),
+                $template->getTemplateName(),
                 $content,
                 $fieldDescription->getFieldName()
             );
@@ -126,23 +151,40 @@ class SonataAdminExtension extends \Twig_Extension
      *
      * @param \Sonata\AdminBundle\Filter\FilterInterface $filter
      * @param array $params
-     * @return
+     * @return string
      */
     public function renderFilterElement(FilterInterface $filter, array $params = array())
     {
-        $description = $filter->getFieldDescription();
+        $fieldDescription = $filter->getFieldDescription();
 
-        $template = $this->environment->loadTemplate($description->getTemplate());
+        $template = $this->getTemplate($fieldDescription, 'SonataAdminBundle:CRUD:base_filter_field.html.twig');
 
-        return $template->render(array_merge($params, array(
+        return $this->output($fieldDescription, $template, array_merge($params, array(
             'filter'        => $filter,
             'filter_form'   => $filter->getField()->createView()
         )));
     }
 
     /**
-     * render a field element from the FieldDescription
+     * render a view element
      *
+     * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
+     * @param mixed $object
+     * @return string
+     */
+    public function renderViewElement(FieldDescriptionInterface $fieldDescription, $object)
+    {
+        $template = $this->getTemplate($fieldDescription, 'SonataAdminBundle:CRUD:base_view_field.html.twig');
+
+        return $this->output($fieldDescription, $template, array(
+            'field_description' => $fieldDescription,
+            'object'            => $object,
+            'value'             => $fieldDescription->getValue($object)
+        ));
+    }
+
+    /**
+     * render a field element from the FieldDescription
      *
      * @throws InvalidArgumentException
      * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
@@ -184,15 +226,15 @@ class SonataAdminExtension extends \Twig_Extension
             $base_template = sprintf('SonataAdminBundle:CRUD:base_%s_edit_field.html.twig', $params['edit']);
         }
 
-        $template = $this->environment->loadTemplate($fieldDescription->getTemplate());
+        $template = $this->getTemplate($fieldDescription, 'SonataAdminBundle:CRUD:base_standard_edit_field.html');
 
-        return $this->output($fieldDescription, $template->render(array_merge($params, array(
+        return $this->output($fieldDescription, $template, array_merge($params, array(
             'admin'             => $fieldDescription->getAdmin(),
             'object'            => $object,
             'field_description' => $fieldDescription,
             'value'             => $this->getValueFromFieldDescription($object, $fieldDescription, $params),
             'field_element'     => $children,
             'base_template'     => $fieldDescription->getOption('base_template', $base_template)
-        ))));
+        )));
     }
 }