I'm editing the content type 'Blog Post' and I've added my own field (type taxonomy). Now I want to make this a required field, so it's mandatory when a user adds a new blog post.
How do I do this?
I believe you'll have to create and handle a "Required" property yourself - you should be able to work this out from looking at how it's done for all the inbuilt fields in the Orchard.Fields project. For example, in the Driver for the MediaPickerField:
protected override DriverResult Editor(ContentPart part, Fields.MediaPickerField field, IUpdateModel updater, dynamic shapeHelper) {
// if the model could not be bound, don't try to validate its properties
if (updater.TryUpdateModel(field, GetPrefix(field, part), null, null)) {
var settings = field.PartFieldDefinition.Settings.GetModel<MediaPickerFieldSettings>();
var extensions = String.IsNullOrWhiteSpace(settings.AllowedExtensions)
? new string[0]
: settings.AllowedExtensions.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
if (extensions.Any() && field.Url != null && !extensions.Any(x => field.Url.EndsWith(x, StringComparison.OrdinalIgnoreCase))) {
updater.AddModelError("Url", T("The field {0} must have one of these extensions: {1}", field.Name.CamelFriendly(), settings.AllowedExtensions));
}
if (settings.Required && String.IsNullOrWhiteSpace(field.Url)) {
updater.AddModelError("Url", T("The field {0} is mandatory", field.Name.CamelFriendly()));
}
}
return Editor(part, field, shapeHelper);
}
Note that just before the editor is returned, it checks if the field has been set to required, and if it has and there's noting provided it calls updater.AddModelError(). I imagine you'll have to implement the Required property for your Taxonomy field, too.
Related
This is a Shopware 6 question. I want to extend the PromotionEntity by adding a max_budget field and display it as a form field in administration. Currently only max_redemptions_global and max_redemptions_per_customer fields exist. The max_budget field should appear in administration right under max_redemptions_global and max_redemptions_per_customer fields. max_budget would act similar to the other two. If the discount for this promotion from total orders combined reaches the value from max_budget, then the promotion does not work anymore.
So I created an entity extension as following:
class PromotionMaxBudgetExtension extends EntityExtension
{
public function extendFields(FieldCollection $collection): void
{
$collection->add(
new OneToOneAssociationField('maxBudget', 'id', 'promotion_id', PromotionMaxBudgetDefinition::class, false)
);
}
public function getDefinitionClass(): string
{
return PromotionDefinition::class;
}
}
Then the definition of the extension:
class PromotionMaxBudgetDefinition extends EntityDefinition
{
public const ENTITY_NAME = 'promotion_max_budget';
public function getEntityName(): string
{
return self::ENTITY_NAME;
}
public function getEntityClass(): string
{
return PromotionMaxBudgetEntity::class;
}
protected function defineFields(): FieldCollection
{
return new FieldCollection([
(new IdField('id', 'id'))->addFlags(new Required(), new PrimaryKey()),
(new FkField('promotion_id', 'promotionId', PromotionDefinition::class)),
(new IntField('max_budget', 'maxBudget')),
(new IntField('left_budget', 'leftBudget')),
(new OneToOneAssociationField('promotion', 'promotion_id', 'id', PromotionDefinition::class, false))
]);
}
}
In order to display the field I had to override the sw-promotion-v2-detail-base. So I modified the sw-promotion-v2-detail-base.html.twig like this:
{% block sw_promotion_v2_detail_base_general_max_uses_customer %}
{% parent %}
<sw-number-field
v-model="??????"
class="sw-promotion-v2-detail-base__field-max-uses-per-customer"
number-type="int"
:label="Max budget"
:placeholder="$tc('sw-promotion-v2.detail.base.general.maxUsesPerCustomerPlaceholder')"
:disabled="!acl.can('promotion.editor')"
allow-empty
/>
{% endblock %}
So, my question would be, how would I tell Shopware this field is for the max_budget entity extension, so that it would save the changes I make to it, at onSave ? Seems like even though the entity extension exists, it is not fetched as an association for the promotion (even with autoload true).
Ok, so I saw how this needs to be done to work perfectly.
Firstly I had to override sw-promotion-v2-detail component and add this js:
Shopware.Component.override('sw-promotion-v2-detail', {
computed: {
promotionCriteria() {
const criteria = this.$super('promotionCriteria');
criteria.addAssociation('maxBudget');
return criteria;
}
},
});
Secondly, in the sw-promotion-v2-detail-base.html.twig the field will look like this:
<sw-number-field
v-if="promotion.extensions.maxBudget"
v-model="promotion.extensions.maxBudget.maxBudget"
class="sw-promotion-v2-detail-base__field-max-budget"
number-type="int"
:label="$tc('vouchers.maxBudget')"
:placeholder="$tc('sw-promotion-v2.detail.base.codes.individual.generateModal.labelPrefix')"
:disabled="!acl.can('promotion.editor')"
allow-empty
/>
Notice the v-model changed to promotion.extensions.....
So the answer would be this:
To add a Shopware 6 form field for an entity extension in admin you have to add the entity association for the extension to the component where the extended entity is fetched (in this case sw-promotion-v2-detail).
After that you can use the extension in twig with promotion.extensions.extensionName....
I need information on how to use the PXSubordinateSelector attribute with the Where type that you can allegedly set on the attribute. Does anybody know how to use this attribute?
Specifically, I need to filter the selector by a custom field in the EPCompanyTree table if possible. Not sure what tables this selector attribute usese. It seems to be tucked into the Acumatica black box. Something like this:
[PXSubordinateSelector(typeof(Where<EPCompanyTree.customField, Equal<{somevalue}>>))]
I've tried setting the Where to an arbitrary filter on the EPCompanyTree.sortorder field but, I'm getting an "is not bound" error when clicking on the lookup.
TIA!
The reason for this error is the defined Search in the GetCommand method of the PXSubordinateSelectorAttribute. Below is the disassembled code of that method:
private static Type GetCommand(Type where)
{
Type whereType = typeof(Where<CREmployee.userID, Equal<Current<AccessInfo.userID>>, Or<EPCompanyTreeMember.workGroupID, Owned<Current<AccessInfo.userID>>>>);
if (where != null)
{
whereType = BqlCommand.Compose(new Type[]
{
typeof(Where2<, >),
typeof(Where<CREmployee.userID, Equal<Current<AccessInfo.userID>>, Or<EPCompanyTreeMember.workGroupID, Owned<Current<AccessInfo.userID>>>>),
typeof(And<>),
where
});
}
return BqlCommand.Compose(new Type[]
{
typeof(Search5<, , , >),
typeof(CREmployee.bAccountID),
typeof(LeftJoin<EPCompanyTreeMember, On<EPCompanyTreeMember.userID, Equal<CREmployee.userID>>>),
whereType,
typeof(Aggregate<GroupBy<CREmployee.acctCD>>)
});
}
As you can see from code the Search is being organized on CREmployee with left joined EPCompanyTreeMember, meanwhile, your code is trying to add a condition on EPCompanyTree field which is not participating in the Search.
I have a products.impex file with an attribute gender=MALE or FEMALE in the itemtype MyProduct(which extends Product) I have an attribute "choice" which depends on the values in the gender column so I initially wrote a PrepareInterceptor and checked for isNew condition.Now it works fine for the new rows but when the value is changed it does not work.Should I just remove the isNew condition or use InitDefaultsInterceptor?
if (ctx.isRemoved(productModel))
{
//TODO
}
else if (ctx.isNew(productModel) || ctx.isModified(productModel, ProductModel.GENDER))
{
//TODO
}
As far as interceptor concern, you can use PrepareInterceptor for preparing fields value as it called before ValidateInterceptor. If you just want to validate your fields then use ValidateInterceptor. The Init Defaults Interceptor is called when a model is filled with its default values.
Have a look at interceptor life cycle.
First - disclaimer: I know I should not do it like this, but use LazyField instead and that model should not contain logic and I will modify my code accordingly, but I wanted to explore the 1-n relationship between content items and orchard in general.
I'm creating a system where user can respond to selected job offer, so I have two content types - Job, which lists all available jobs, and JobAnswer which contains my custom part and provides link to appropriate Job content item:
public class JobPart : ContentPart<JobPartRecord>
{
public ContentItem Job
{
get
{
if (Record.ContentItemRecord != null)
{
var contentItem = ContentItem.ContentManager.Get(Record.Job.Id);
return contentItem;
}
var nullItem = ContentItem.ContentManager.Query("Job").List().First();
return nullItem;
}
set { Record.Job = value.Record; }
}
}
This works, but I'm not sure how should I handle returning a null contentItem, when creating new content item, now it just returns first Job content item, which is far from ideal.
In a Model_Page class, extending the Kohana ORM class, I have this rules definition :
public function rules() {
return array(
'url' => array(
array('Model_Page::unique_url', array($this)),
),
);
}
To simplify here, I will just return false from this function, so it should never validate when I try to save/update a page :
public static function unique_url($page) {
return false;
}
This works as expected, if the value for url is not NULL or not an empty string.
But if I already have a page with an empty url, and that I try to add a new page with an empty url, the unique_url function is ignored, even when forcing a return false.
This could be a bug, but maybe I missed something...? In the Kohana docs, for the unique example, they use a username as an example, but the username also has a not_empty rule, which does not apply here.
Any help/suggestion appreciated!
I believe the rule is applied once you set the value, not when you're saving it.
I had a similar issue - the filter wasn't working if I didn't assign any value to the field. I've written my own save method:
public function save(Validation $validation = NULL)
{
if (!$this->loaded())
{
$this->ordering = 0;
}
return parent::save($validation);
}
this way the ordering would always be assigned for newly created objects and my filter would work.
And that's how I built another model. It's a company model that has a unique company name. Rules for the field are defined like this:
'name' => array(
array('not_empty'),
array('max_length', array(':value', 255)),
array(array($this, 'unique_name'))
)
And I have a method:
public function unique_name($value)
{
$exists = (bool) DB::select(array(DB::expr('COUNT(*)'), 'total_count'))
->from($this->_table_name)
->where('name', '=', $value)
->where($this->_primary_key, '!=', $this->pk())
->execute($this->_db)
->get('total_count');
return !$exists;
}
It basically checks if there are any other companies with the same name as the current one. Maybe this will give you the idea of what could be wrong with your solution.