Edit: searching appears to work in a very basic manner, searching for whatever text is entered in all the String member fields of the relevant model, with no special search operators.
If there's any way to customize how the search works, I'd like to know. Right now it's looking like I'll just have to ignore the built-in search stuff and roll my own.
Since adding the notes field, it's started returning nothing instead of everything.
The only thing that was worked so far is entering "test" in the search box, which returns the record with "test" in the notes field. Searching for "notes:test" or "notes=test" doesn't work, nor does any attempt to search the agent or status fields. I've tried things like "1400" which is a value I've verified is present in the agent field via phpMyAdmin, and things like "0" or "VALID" for the status field, which is what all the records have.
model fields:
public class AgentShift extends Model {
public enum Status { VALID, RESCHEDULED, EXTENDED, DELETED } // REDUCED?
#Required #Min(1000) #Max(99999)
public int agent;
public Status status = Status.VALID;
#Required
#Columns(columns={#Column(name="start_time"),#Column(name="end_time")})
#Type(type="org.joda.time.contrib.hibernate.PersistentInterval")
public Interval scheduled;
#Type(type="org.joda.time.contrib.hibernate.PersistentLocalTimeAsTime")
public LocalTime agent_in;
#Type(type="org.joda.time.contrib.hibernate.PersistentLocalTimeAsTime")
public LocalTime agent_out;
public String notes;
}
modified crud list page:
#{extends 'main.html' /}
#{set title: 'Agent Shifts' /}
<div id="crudList" class="${type.name}">
<h2 id="crudListTitle">&{'crud.list.title', type.name}</h2>
<div id="crudListSearch">
#{crud.search /}
</div>
<div id="crudListTable">
#{crud.table fields:['id','agent','scheduled','status','notes'] }
#{crud.custom 'scheduled'}
#{if object.scheduled.getStart().toDateMidnight().equals(object.scheduled.getEnd().toDateMidnight())}
${org.joda.time.format.DateTimeFormat.forStyle("SS").printTo(out, object.scheduled.getStart())} to
${org.joda.time.format.DateTimeFormat.forStyle("-S").printTo(out, object.scheduled.getEnd())}
#{/if}
#{else}
${org.joda.time.format.DateTimeFormat.forStyle("SS").printTo(out, object.scheduled.getStart())} to
${org.joda.time.format.DateTimeFormat.forStyle("SS").printTo(out, object.scheduled.getEnd())}
#{/else}
#{/crud.custom}
*{
#{crud.custom 'entered_by'}
#{if object.entered_by}
${object.entered_by}
#{/if}
#{else} ?
#{/else}
#{/crud.custom}
#{crud.custom 'created'}
${org.joda.time.format.DateTimeFormat.forStyle("SS").printTo(out, object.created)}
#{/crud.custom}
}*
#{/crud.table}
</div>
<div id="crudListPagination">
#{crud.pagination /}
</div>
<p id="crudListAdd">
&{'crud.add', type.modelName}
</p>
</div>
Here you can see how to customize the CRUD pages, including how to show certain fields of the object in the screen.
The search strings are supposed to be in the format:
<field name>:<value>
For example:
name:Liam
That search would filter all objects whose name contains Liam. The field name must be a field displayed in the list page. I'm not sure if it works with #Lob, but usually that's not a field you want to display in the list page so it shouldn't be an issue.
I have to say that in Play 1.1 I had some issues with some columns, in which starting the search didn't work (it raised an error) and I couldn't solve it. It doesn't seem to happen in 1.2.1 (I have no idea if it is a fix or simply a change I did without noticing)
EDIT:
On the updated question, the list page seems right.
One bold idea: have you checked that the database has the proper columns? I remember I had some issues with Hibernate not playing fair when I changed some models and a few columns where not updated properly, causing odd behavior. It may be worth it to remove the table completely and let Play recreate it.
If that doesn't solve it most likely this is a Play bug in the CRUD controller, you would need to find the source.
My first concern would be the lack on a rel annotation on Interval, and the usage of LocalTime and the enum Status. It should not matter, but... I'm afraid I can only suggest you to do an incremental test to locale the issue:
remove everything except agent and notes, and try the search.
If it fails, raise a bug on Play's Lighthouse as it shouldn't
If it works, keep adding fields one at a time and retesting the search. This will be fast with Play, and you may detect the annotation/field causing the issue and report on Play's Lighthouse
You can use the elastic search module.
1) Define Controller:
#ElasticSearchController.For(ElasticSearchSampleModel.class)
public class ElasticSearchExample extends ElasticSearchController {
}
2) Define standard JPA model with #ElasticSearchable annotation:
#Entity
#ElasticSearchable
public class ElasticSearchSampleModel extends Model {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The field1. */
public String field1;
/** The field2. */
public String field2;
/**
* To String
*
* #see play.db.jpa.JPABase#toString()
*/
#Override
public String toString() {
return "ElasticSearchSampleModel [field1=" + this.field1 + ", field2=" + this.field2 + "]";
}
}
You can find more information at http://geeks.aretotally.in/play-framework-module-elastic-search-distributed-searching-with-json-http-rest-or-java.
Related
I have a view that creates one main object (Author) and a list of other objects for it (Books). So the created object can return the books by calling author.Books.ToList() for example.
My problem is that I only want users to be able to set certain attributes of the book (name, date etc.). I do not want them to be able to inject the form with javascript and set the Price of a book.
How do I tell the framework that I want to bind author.Books[all].Name (and date), but want to discard author.Books[all].Price?
I know I could just manually test it in the controller, but I felt like there is a better solution and I just can't quite put my finger on it.
Some code for context:
An inputbox from the View:
<input data-val="true" data-val-required="The CanBeBorrowed field is required." id="books_0__CanBeBorrowed" name="books[0].CanBeBorrowed" type="checkbox" value="true"`>
You can see how adding extra input fields would corrupt the data.
The controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Date,Books")]Author author {...}
(In my project, I have different classes with the same structure. That is why it looks silly creating all the books when creating the author.)
This exactly why we need to use ViewModels,
you can just set in ViewModel
[Editable(false)]
public decimal Price { get; set; }
and also in your
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([AuthorViewModel authorVm)
{
var author = _repository.getById(authorVm.Id);
//update only the fields of author object that user is allowed to update.
author.Name = authorVm.Name;
author.Date = authorVm.Date;
}
you can read more about ViewModels and how to use them
here and here
Good day!
In my Orchard, I have several content types all with my custom part attached. This part defines to what users this content is available. For each logged user there is external service, which defines what content user can or cannot access. Now I need access restriction to apply everywhere where orchard display content lists, this includes results by specific tag from a tag cloud, or results listed from Taxonomy term. I seems can’t find any good way to do it except modifying TaxonomyServices code as well as TagCloud services, to join also my part and filter by it. Is this indeed the only way to do it or there are other solutions? I would like to avoid doing changes to built-in modules if possible but cannot find other way.
Thanks in advance.
I'm currently bumbling around with the same issue. One way I'm currently looking at is to hook into the content manager.
[OrchardSuppressDependency("Orchard.ContentManagement.DefaultContentManager")]
public class ModContentManager : DefaultContentManager, IContentManager
{
//private readonly Lazy<IShapeFactory> _shapeFactory;
private readonly IModAuthContext _modAuthContext;
public ModContentManager(IComponentContext context,
IRepository<ContentTypeRecord> contentTypeRepository,
IRepository<ContentItemRecord> contentItemRepository,
IRepository<ContentItemVersionRecord> contentItemVersionRepository,
IContentDefinitionManager contentDefinitionManager,
ICacheManager cacheManager,
Func<IContentManagerSession> contentManagerSession,
Lazy<IContentDisplay> contentDisplay,
Lazy<ISessionLocator> sessionLocator,
Lazy<IEnumerable<IContentHandler>> handlers,
Lazy<IEnumerable<IIdentityResolverSelector>> identityResolverSelectors,
Lazy<IEnumerable<ISqlStatementProvider>> sqlStatementProviders,
ShellSettings shellSettings,
ISignals signals,
//Lazy<IShapeFactory> shapeFactory,
IModAuthContext modAuthContext)
: base(context,
contentTypeRepository,
contentItemRepository,
contentItemVersionRepository,
contentDefinitionManager,
cacheManager,
contentManagerSession,
contentDisplay,
sessionLocator,
handlers,
identityResolverSelectors,
sqlStatementProviders,
shellSettings,
signals) {
//_shapeFactory = shapeFactory;
_modAuthContext = modAuthContext;
}
public new dynamic BuildDisplay(IContent content, string displayType = "", string groupId = "") {
// So you could do something like...
// var myPart = content.As<MyAuthoPart>();
// if(!myPart.IsUserAuthorized)...
// then display something else or display nothing (I think returning null works for this but
//don't quote me on that. Can always return a random empty shape)
// else return base.BuildDisplay(content, displayType, groupId);
// ever want to display a shape based on the name...
//dynamic shapes = _shapeFactory.Value;
}
}
}
Could also hook into the IAuthorizationServiceEventHandler, which is activated before in the main ItemController and do a check to see if you are rendering a projection or taxonomy list set a value to tell your content manager to perform checks else just let them through. Might help :)
I'm using play framework 1.2.7 to create a simple page to search some data on a database.
I already have one of the listing pages with CRUD module. The problem is that the search is a text field that searches in all text columns. I want to customize this.
The default is:
#{crud.search /}
I imagine I should be able to do something like:
#{crud.search }
... search fields...
#{/crud.search}
But I can't find any documentation about it.
How can I define the fields to search and how to use them?
What it worked for me was to overwrite the list method in the controller that extends from CRUD.
For example:
public static void list(int page, String search, String searchFields,
String orderBy, String order) {
ObjectType type = ObjectType.get(getControllerClass());
notFoundIfNull(type);
if (page < 1) {
page = 1;
}
List<YourObject> yourObjects;
List<Model> objects;
yourObjects = YourObject.yourSearch(search);
/* I also wanted to keep the standard search
so from here I also kept the standard code */
....
}
Lets say I want a different main image for each page, situated above the page title. Also, I need to place page specific images in the left bar, and page specific text in the right bar. In the right and left bars, I also want layer specific content.
I can't see how I can achieve this without creating a layer for each and every page in the site, but then I end up with a glut of layers that only serve one page which seems too complex.
What am I missing?
If there is a way of doing this using Content parts, it would be great if you can point me at tutorials, blogs, videos to help get my head round the issue.
NOTE:
Sitefinity does this sort of thing well, but I find Orchard much simpler for creating module, as well as the fact that it is MVC which I find much easier.
Orchard is free, I understand (and appreciate) that. Just hoping that as the product evolves this kind of thing will be easier?
In other words, I'm hoping for the best of all worlds...
There is a feature in the works for 1.5 to make that easier, but in the meantime, you can already get this to work quite easily with just a little bit of code. You should first add the fields that you need to your content type. Then, you are going to send them to top-level layout zones using placement. Out of the box, placement only targets local content zones, but this is what we can work around with a bit of code by Pete Hurst, a.k.a. randompete. Here's the code:
ZoneProxyBehavior.cs:
=====================
using System;
using System.Collections.Generic;
using System.Linq;
using ClaySharp;
using ClaySharp.Behaviors;
using Orchard.Environment.Extensions;
namespace Downplay.Origami.ZoneProxy.Shapes {
[OrchardFeature("Downplay.Origami.ZoneProxy")]
public class ZoneProxyBehavior : ClayBehavior {
public IDictionary<string, Func<dynamic>> Proxies { get; set; }
public ZoneProxyBehavior(IDictionary<string, Func<dynamic>> proxies) {
Proxies = proxies;
}
public override object GetMember(Func<object> proceed, object self, string name) {
if (name == "Zones") {
return ClayActivator.CreateInstance(new IClayBehavior[] {
new InterfaceProxyBehavior(),
new ZonesProxyBehavior(()=>proceed(), Proxies, self)
});
}
// Otherwise proceed to other behaviours, including the original ZoneHoldingBehavior
return proceed();
}
public class ZonesProxyBehavior : ClayBehavior {
private readonly Func<dynamic> _zonesActivator;
private readonly IDictionary<string, Func<dynamic>> _proxies;
private object _parent;
public ZonesProxyBehavior(Func<dynamic> zonesActivator, IDictionary<string, Func<dynamic>> proxies, object self) {
_zonesActivator = zonesActivator;
_proxies = proxies;
_parent = self;
}
public override object GetIndex(Func<object> proceed, object self, IEnumerable<object> keys) {
var keyList = keys.ToList();
var count = keyList.Count();
if (count == 1) {
// Here's the new bit
var key = System.Convert.ToString(keyList.Single());
// Check for the proxy symbol
if (key.Contains("#")) {
// Find the proxy!
var split = key.Split('#');
// Access the proxy shape
return _proxies[split[0]]()
// Find the right zone on it
.Zones[split[1]];
}
// Otherwise, defer to the ZonesBehavior activator, which we made available
// This will always return a ZoneOnDemandBehavior for the local shape
return _zonesActivator()[key];
}
return proceed();
}
public override object GetMember(Func<object> proceed, object self, string name) {
// This is rarely called (shape.Zones.ZoneName - normally you'd just use shape.ZoneName)
// But we can handle it easily also by deference to the ZonesBehavior activator
return _zonesActivator()[name];
}
}
}
}
And:
ZoneShapes.cs:
==============
using System;
using System.Collections.Generic;
using Orchard.DisplayManagement.Descriptors;
using Orchard;
using Orchard.Environment.Extensions;
namespace Downplay.Origami.ZoneProxy.Shapes {
[OrchardFeature("Downplay.Origami.ZoneProxy")]
public class ZoneShapes : IShapeTableProvider {
private readonly IWorkContextAccessor _workContextAccessor;
public ZoneShapes(IWorkContextAccessor workContextAccessor) {
_workContextAccessor = workContextAccessor;
}
public void Discover(ShapeTableBuilder builder) {
builder.Describe("Content")
.OnCreating(creating => creating.Behaviors.Add(
new ZoneProxyBehavior(
new Dictionary<string, Func<dynamic>> { { "Layout", () => _workContextAccessor.GetContext().Layout } })));
}
}
}
With this, you will be able to address top-level layout zones using Layout# in front of the zone name you want to address, for example Layout#BeforeContent:1.
ADDENDUM:
I have used Bertrand Le Roy's code (make that Pete Hurst's code) and created a module with it, then added 3 content parts that are all copies of the bodypart in Core/Common.
In the same module I have created a ContentType and added my three custom ContentParts to it, plus autoroute and bodypart and tags, etc, everything to make it just like the Orchard Pages ContentType, only with more Parts, each with their own shape.
I have called my ContentType a View.
So you can now create pages for your site using Views. You then use the ZoneProxy to shunt the custom ContentPart shapes (Parts_MainImage, Parts_RightContent, Parts_LeftContent) into whatever Zones I need them in. And job done.
Not quite Sitefinity, but as Bill would say, Good enough.
The reason you have to create your own ContentParts that copy BodyPart instead of just using a TextField, is that all TextFields have the same Shape, so if you use ZoneProxy to place them, they all end up in the same Zone. Ie, you build the custom ContentParts JUST so that you get the Shapes. Cos it is the shapes that you place with the ZoneProxy code.
Once I have tested this, I will upload it as a module onto the Orchard Gallery. It will be called Wingspan.Views.
I am away on holiday until 12th June 2012, so don't expect it before the end of the month.
But essentially, with Pete Hurst's code, that is how I have solved my problem.
EDIT:
I could have got the same results by just creating the three content parts (LeftContent, RightContent, MainImage, etc), or whatever content parts are needed, and then adding them to the Page content type.
That way, you only add what is needed.
However, there is some advantage in having a standard ContentType that can be just used out of the box.
Using placement (Placement.info file) you could use the MainImage content part for a footer, for example. Ie, the names should probably be part 1, part 2, etc.
None of this would be necessary if there was a way of giving the shape produced by the TextField a custom name. That way, you could add as may TextFields as you liked, and then place them using the ZoneProxy code. I'm not sure if this would be possible.
Im using subsonic 2.2
I tried asking this question another way but didnt get the answer i was looking for.
Basically i ususally include validation at page level or in my code behind for my user controls or aspx pages. However i haev seen some small bits of info advising this can be done within partial classes generated from subsonic.
So my question is, where do i put these, are there particular events i add my validation / business logic into such as inserting, or updating. - If so, and validation isnt met, how do i stop the insert or update. And if anyone has a code example of how this looks it would be great to start me off.
Any info greatly appreciated.
First you should create a partial class for you DAL object you want to use.
In my project I have a folder Generated where the generated classes live in and I have another folder Extended.
Let's say you have a Subsonic generated class Product. Create a new file Product.cs in your Extended (or whatever) folder an create a partial class Product and ensure that the namespace matches the subsonic generated classes namespace.
namespace Your.Namespace.DAL
{
public partial class Product
{
}
}
Now you have the ability to extend the product class. The interesting part ist that subsonic offers some methods to override.
namespace Your.Namespace.DAL
{
public partial class Product
{
public override bool Validate()
{
ValidateColumnSettings();
if (string.IsNullOrEmpty(this.ProductName))
this.Errors.Add("ProductName cannot be empty");
return Errors.Count == 0;
}
// another way
protected override void BeforeValidate()
{
if (string.IsNullOrEmpty(this.ProductName))
throw new Exception("ProductName cannot be empty");
}
protected override void BeforeInsert()
{
this.ProductUUID = Guid.NewGuid().ToString();
}
protected override void BeforeUpdate()
{
this.Total = this.Net + this.Tax;
}
protected override void AfterCommit()
{
DB.Update<ProductSales>()
.Set(ProductSales.ProductName).EqualTo(this.ProductName)
.Where(ProductSales.ProductId).IsEqualTo(this.ProductId)
.Execute();
}
}
}
In response to Dan's question:
First, have a look here: http://github.com/subsonic/SubSonic-2.0/blob/master/SubSonic/ActiveRecord/ActiveRecord.cs
In this file lives the whole logic I showed in my other post.
Validate: Is called during Save(), if Validate() returns false an exception is thrown.
Get's only called if the Property ValidateWhenSaving (which is a constant so you have to recompile SubSonic to change it) is true (default)
BeforeValidate: Is called during Save() when ValidateWhenSaving is true. Does nothing by default
BeforeInsert: Is called during Save() if the record is new. Does nothing by default.
BeforeUpdate: Is called during Save() if the record is new. Does nothing by default.
AfterCommit: Is called after sucessfully inserting/updating a record. Does nothing by default.
In my Validate() example, I first let the default ValidatColumnSettings() method run, which will add errors like "Maximum String lenght exceeded for column ProductName" if product name is longer than the value defined in the database. Then I add another errorstring if ProductName is empty and return false if the overall error count is bigger than zero.
This will throw an exception during Save() so you can't store the record in the DB.
I would suggest you call Validate() yourself and if it returns false you display the elements of this.Errors at the bottom of the page (the easy way) or (more elegant) you create a Dictionary<string, string> where the key is the columnname and the value is the reason.
private Dictionary<string, string> CustomErrors = new Dictionary<string, string>
protected override bool Validate()
{
this.CustomErrors.Clear();
ValidateColumnSettings();
if (string.IsNullOrEmpty(this.ProductName))
this.CustomErrors.Add(this.Columns.ProductName, "cannot be empty");
if (this.UnitPrice < 0)
this.CustomErrors.Add(this.Columns.UnitPrice, "has to be 0 or bigger");
return this.CustomErrors.Count == 0 && Errors.Count == 0;
}
Then if Validate() returns false you can add the reason directly besides/below the right field in your webpage.
If Validate() returns true you can safely call Save() but keep in mind that Save() could throw other errors during persistance like "Dublicate Key ...";
Thanks for the response, but can you confirm this for me as im alittle confused, if your validating the column (ProductName) value within validate() or the beforevalidate() is string empty or NULL, doesnt this mean that the insert / update has already been actioned, as otherwise it wouldnt know that youve tried to insert or update a null value from the UI / aspx fields within the page to the column??
Also, within asp.net insert or updating events we use e.cancel = true to stop the insert update, if beforevalidate failes does it automatically stop the action to insert or update?
If this is the case, isnt it eaiser to add page level validation to stop the insert or update being fired in the first place.
I guess im alittle confused at the lifecyle for these methods and when they come into play