Suppose we have one class in the model from which several other classes are inherited. Now we define also an admin class to control the layout in the admin interface.
We can make all other admin classes inherite (just as we did in the model), but how can we overwrite the fieldsets to add some new values?
Example:
in model file:
class A(models.Model):
field1 = models.TextField(..)
field2 = models.TextField(..)
class B(A):
field3 = models.TextField(..)
in admin file:
class A_Admin(admin.ModelAdmin):
fieldsets = (
(None, {
'fields': (( 'field1', 'field2'))
}),
)
class B_Admin(A_Admin):
pass # here I notice that it takes the fields from A_admin, I would like to add my field 3 without rewriting the fieldsets
admin.site.register(A, A_Admin)
admin.site.register(B, B_Admin)
the resulting fieldsets would be :
(
(None, {
'fields': (( 'field1', 'field2'))
}),
('Specific to B', {
'fields': ('field3')
}),
)
I don't know if it is a neat solution but it should do what you want.
class A_Admin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['field1', 'field2']}),
]
class B_Admin(A_Admin):
fieldsets = [
('Specific to B', {'fields': ['field3', 'field4']}),
]
fieldsets.insert(0, A_Admin.fieldsets[0])
Related
I have a form in symfony 5:
$builder
->add('name',TextType::class,[
'label'=>'Character Name',
'constraints'=>[
new Regex('/[\w\s]+/')
],
'required'=>false,
'attr'=>[
'class'=>'form-control'
],
'label_attr'=>[
'class'=>'form-label'
]
])->add('gender',ChoiceType::class,[
'label'=>'Gender',
'required'=>false,
'choices'=>[
'Any'=>'',
'Male'=>'Male',
'Female'=>'Female',
'Genderless'=>'Genderless',
'Unknown'=>'Unknown'
],
'attr'=>[
'class'=>'form-control'
],
'label_attr'=>[
'class'=>'form-label'
]
])->add('status',ChoiceType::class,[
'label'=>'Status',
'required'=>false,
'choices'=>[
'Any'=>'',
'Alive'=>'Alive',
'Dead'=>'Dead',
'Unknown'=>'unknown'
],
'attr'=>[
'class'=>'form-control'
],
'label_attr'=>[
'class'=>'form-label'
]
])->add('species',ChoiceType::class,[
'label'=>'Species',
'required'=>false,
'choices'=>[
'Any'=>'',
'Human'=>'Human',
'Alien'=>'Alien'
],
'attr'=>[
'class'=>'form-control'
],
'label_attr'=>[
'class'=>'form-label'
]
])->add('submit',SubmitType::class,[
'label'=>'Filter Results',
'attr'=>[
'class'=>'btn btn-primary'
]
]);
What i want to do if possible is use regex to strip special characters from the "name" field after it's submitted so the resulting field value only contains alphanumeric and spaces, so i want to run this on it:
preg_replace('/[^\w\s]/','',$name);
The closest thing i can find to do this is a model transformer but that doesn't really suit this situation as it's just a one way action.
You could use an EventSubscriber, just like Symfony does internally to trim the value in their TextType field (see https://github.com/symfony/symfony/blob/9045ad4bf2837e302e7cdbe41c38f1af33cbe854/src/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php ):
<?php
namespace App\Form\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class SanitizeListener implements EventSubscriberInterface
{
public function preSubmit(FormEvent $event)
{
$data = $event->getData();
if (!\is_string($data)) {
return;
}
$event->setData(preg_replace('/[^\w\s]/','',$data));
}
public static function getSubscribedEvents(): array
{
return [FormEvents::PRE_SUBMIT => 'preSubmit'];
}
}
Attach the listener to your name field like this:
$builder->get('name')->addEventSubscriber(new SanitizeListener());
I have a form (Zend\Form\Form) with some nested fieldsets (Zend\Form\Fieldset) in it. The construction is pretty similar to that in the Form Collections tutorial.
Storage\Form\MyForm
|_'Storage\Form\Fieldset\FooFieldset'
|_'Storage\Form\Fieldset\BarFieldset'
|_'Storage\Form\Fieldset\BazFieldset'
...
MyForm
class MyForm {
public function __construct()
{
...
$this->add(
[
'type' => 'Storage\Form\Fieldset\FooFieldset',
'options' => [
'use_as_base_fieldset' => true
]
]
);
}
}
FooFieldset
class FooFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct()
{
parent::__construct('foo');
$this->setHydrator(new ClassMethodsHydrator())->setObject(new Foo()); // dependencies!
}
}
It works, but the fieldset class has two dependencies in it. I want to inject them. In order to do it, I created a FooFieldsetFactory and extended the /module/MyModule/config/module.config.php by:
'service_manager' => [
'factories' => [
'Storage\Form\Fieldset\FooFieldset' => 'Storage\Form\Fieldset\Factory\FooFieldsetFactory',
],
],
The factory is simply being ignored. I guess, the service locator first tries to find the class by namespace and only if nothing is found, takes a look in the invokables and factories. OK. Then I created an alias:
'service_manager' => [
'factories' => [
'Storage\Form\Fieldset\FooFieldset' => 'Storage\Form\Fieldset\Factory\FooFieldsetFactory',
],
],
'aliases' => [
'Storage\Form\Fieldset\Foo' => 'Storage\Form\Fieldset\FooFieldset',
],
... and tried to use it instead of Storage\Form\Fieldset\FooFieldset in my form class. But now I get an exception:
Zend\Form\FormElementManager::get was unable to fetch or create an instance for Storage\Form\Fieldset\Foo
I've also tried this directly:
'service_manager' => [
'factories' => [
'Storage\Form\Fieldset\Foo' => 'Storage\Form\Fieldset\Factory\FooFieldsetFactory',
],
],
No effect, the same error.
And this also didn't work (the same error):
'form_elements' => [
'factories' => [
'Storage\Form\Fieldset\Foo' => 'Storage\Form\Fieldset\Factory\FooFieldsetFactory',
],
],
So the referencing a service for a fieldset seems not to work. Or am I doing something wrong?
How to use services for form fieldsets?
UPDATE
With some debuggin I found out, that my Foo fieldset factory cannot be found, because it has not be added to the factories list of the Zend\Form\FormElementManager. Here is the place in the Zend\Form\Factory:
So my config
'form_elements' => [
'factories' => [
'Storage\Form\Fieldset\Foo' => 'Storage\Form\Fieldset\Factory\FooFieldsetFactory',
],
],
is ignored. How to fix this?
UPDATE Additional information, how I'm creating my Form object.
/module/Foo/config/module.config.php
return [
'controllers' => [
'factories' => [
'Foo\Controller\My' => 'Foo\Controller\Factory\MyControllerFactory'
]
],
'service_manager' => [
'factories' => [
'Foo\Form\MyForm' => 'Foo\Form\Factory\MyFormFactory',
],
],
];
/module/Foo/src/Foo/Form/Factory/MyFormFactory.php
namespace Foo\Form\Factory;
use ...;
class MyFormFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$form = new MyForm();
$form->setAttribute('method', 'post')
->setHydrator(new ClassMethods())
->setInputFilter(new InputFilter());
return $form;
}
}
/module/Foo/src/Foo/Controller/Factory/MyControllerFactory.php
namespace Foo\Controller\Factory;
use ...;
class MyControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$fooPrototype = new Foo();
$realServiceLocator = $serviceLocator->getServiceLocator();
// $myForm = $realServiceLocator->get('Foo\Form\MyForm'); <-- This doesn't work correctly for this case. The FormElementManager should be used instead.
$formElementManager = $realServiceLocator->get('FormElementManager');
$myForm = $formElementManager->get('Foo\Form\MyForm');
return new MyController($myForm, $fooPrototype);
}
}
This issue is because you are adding your form elements in the forms __construct() method rather than init() as suggested in the documentation.
You can use a factory instead of an invokable in order to handle dependencies in your elements/fieldsets/forms.
And now comes the first catch.
If you are creating your form class by extending Zend\Form\Form, you must not add the custom element in the __construct-or (as we have done in the previous example where we used the custom element’s FQCN), but rather in the init() method:
The reason is that the new form's factory (which is used to create new elements using add()) must have the application's form element manager injected after the form's constructor has been called This form element manager instance contains all the references to your custom forms elements which are registered under the form_elements configuration key.
By calling add() in the form __construct the form factory will lazy load a new instance of the form element manager; which will be able to create all default form elements but will not have any knowledge of your custom form element.
Consider these two Grails domain classes:
class Agreement implements Serializable {
String code
String branchCode
...
static belongsTo = [agency: CollectingAgency]
static mapping = {
...
}
}
class CollectingAgency implements Serializable {
String type
String agencyClass
...
static hasMany = [agreement: Agreement]
static mapping = {
...
}
}
Now when I perform Agreement.findAll() it creates a sql similar to this (using loggingSql = true):
select agreement0_.agency_id as agency4_67_1_, agreement0_.AGREH_CD as
AGREH1_1_, agreement0_.AGREH_BRCHCD as AGREH2_1_,
...
agreement0_.agency_id as agency4_66_0_,
^^^^^^^^^
...
from RVAGREHDROTB agreement0_
where agreement0_.agency_id=?
The statement will not execute because of the unknown column(agency_id). And I wonder where do it gets the agency_id column? I haven't mapped any column with such name.
And when I try to query from the other side using CollectingAgency.findAll(), it returns a malformed Object:
[
{
type: "0400300",
agencyClass: "12",
...
agreement: [
Yes, like that, with the agreement key having an opening square bracket [ but no closing bracket ]. Plus, the other properties of the table are not retrieved.
How can I achieve a query with a resulting object similar to these:
Agreement.findAll():
[
{
code: "1212",
branchCode: "a014s",
...
agency: {
type: "0400300",
agencyClass: "12",
...
},
...
},
{
code: "1213",
branchCode: "a014z",
...
agency: {
type: "0400300",
agencyClass: "12",
...
},
...
},
...
]
and CollectingAgency.findAll():
[
{
type: "0400300",
agencyClass: "12",
...
agreement: [
{
code: "1212",
branchCode: "a014s",
...
},
{
code: "1213",
branchCode: "a014z",
...
},
...
]
},
...
]
In your Agreement class you have
static belongsTo = [agency: CollectingAgency]
which will create an 'agency' field in your class. the agency_id you see just maps your 'agency' field to the 'agency' column in the database.
I have the following navigation setup on a grails controller is it possible for List_X and List_Y to go to different actions but then be mapped to the same gsp file?
subItems: [
[ action: 'list_X',title: 'Something', order:1 ],
[ action: 'error_X',title: 'Something',order:2 ],
[ action: 'list_Y', title: 'Something', order:3 ],
[ action: 'error_Y',title: 'Something', order:4 ],
]
You can specify manually which gsp file should be rendered. In action in your controller do this:
def list_X() {
// ... some code
render(view : "listView", model : [name : "bob", items : []])
}
def list_Y() {
//... some code
render(view : "listView", model : [name : "bob", items : []])
}
That way, same gsp template will be used to render result of both actions.
Suppose I have base model class Item
class Item
include Mongoid::Document
field :category
end
Each category determines which fields should item contain. For example, items in "category1" should contain additional string field named text, items in "category2" should contain fields weight and color. All the fields are of basic types: strings, integers and so on.
These additional values are to be stored in mongodb as document's fields:
> db.items.find()
{ "_id" : ObjectId("4d891f5178146536877e1e99"), "category" : "category1", "text" : "blah-blah" }
{ "_id" : ObjectId("4d891f7878146536877e1e9a"), "category" : "category2", "weight" : 20, "color" : "red" }
Categories are stored in the mongodb, too. Fields' configuration is defined at runtime by an administrator.
> db.categories.find()
{ "_id" : "category1", "fields" : [ { "name" : "text", "type" : "String" } ] }
{ "_id" : "category2", "fields" : [
{
"name" : "weight",
"type" : "Integer"
},
{
"name" : "color",
"type" : "String"
}
] }
Users need to edit Items with html forms entering values for all additional fields defined for the category of particular item.
The question is
What approaches could I take to implement this polymorphism on rails?
Please ask for required details with comments.
Just subclass the Item, and Mongoid will take care of rest, e.g. storing type.
class TextItem < Item; end
Rails will like it, but you'll probably want to use #becomes method, as it will make form builder happy and path generation easier:
https://github.com/romanbsd/mongoid/commit/9c2fbf4b7b1bc0f602da4b059323565ab1df3cd6