When a user is not an Admin, but his assigned group has access to a ModelAdmin the model admin page is listed in the menu & the user can visit it, however no records show in the index view.
To show the records, the permissions need to be set in the model. The documentation says to do it like this:
http://doc.silverstripe.org/framework/en/3.1/reference/modeladmin
class Category extends DataObject {
// ...
public function canView($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canEdit($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canDelete($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canCreate($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
}
However this does not work as $member is Null. Setting these methods to return true displays the records. Is this secure? Or does that set anybody to be able to edit the records? logging in as a user whose group does not have access to that model admin seems to not allow them to get to the listing page, but it seems like the wrong thing to do.
public function canView($member = null) {
return null;
}
public function canEdit($member = null) {
return true;
}
public function canDelete($member = null) {
return true;
}
public function canCreate($member = null) {
return true;
}
What is the best way to allow a group to view & edit a modelAdmin's records?
The example is what you want to follow, though with a different permission name. The permission name in the example is if the user has access to CMSMain, which is the part go the CMS that handles pages.
To get the name of the permission, you take the class name of your ModelAdmin (say, CategoryAdmin) and prepend CMS_ACCESS_ to it (which would give CMS_ACCESS_CategoryAdmin in this example).
As for $member being null, that is only the default value. So $member is only null if no value is passed in. This doesn't actually matter though, as Permission::check specifically handles being passed in a null value and uses the current logged in user instead.
Related
I got a problem when debugging my MVC program and I want to acces to my db called "UserActivity".
on the browser, it saying that "The localhost page isn’t working
localhost redirected you too many times."
but without showing the specific error location.
here is my UserActivtyController, GET /UserActivity/Index code:
public class UserActivityController : BaseController
{
//GET /UserActivity/Index
public ActionResult Index(string returnUrl, int page = 1, string sort = "Id", string sortDir = "ASC", string filter = null)
{
String query = #"
SELECT Id
,CreatedBy
,CreatedOn
,ModifiedBy
,ModifiedOn
,ContactId
,EntityName
,EntityId
,ActivityType
,ActivityStatus
,DueDate
,ActualEndDate
,MasqueradeOn
,MasqueradeBy
FROM UserActivity
-- ORDER BY CreatedOn DESC
-- OFFSET (#PageNumber -1) * 30 ROWS
-- FETCH NEXT 30 ROWS ONLY
";
//string countQuery = #""
List<UserActivityModels> userActivity = null;
using (IDbConnection db = new MySqlConnection(ConfigurationManager.ConnectionStrings["CRMPORTALSQLCONN"].ConnectionString))
{
userActivity = (List<UserActivityModels>)db.Query<UserActivityModels>(query, new
{
#PageNumber = page,
});
/*ViewData["TotalCount"] = (int)db.ExecuteScalar(countQuery, new
{
#PageNumber = page,
#Id = string.IsNullOrEmpty(filter) ? null : filter
});
*/
ViewData["PageSize"] = 30;
ViewData["Filter"] = filter;
}
if (userActivity != null)
{
return RedirectToAction(returnUrl);
}
return View(userActivity);
}
}
Really appreciate if there anyone who know something about this problem. Thanks
if (userActivity != null)
{
return RedirectToAction(returnUrl);
}
If the returnUrl points to the same action ("UserActivity/Index") it will create infinite redirect loop. If you want to redirect request to different action make sure you pass correct name.
You have a loop back situation. This is similar to endless while loop. To fix it change your code redirection implementation to redirect to an action method. Notice how I have changed the implementation below. This will fix the issue "localhost redirected you too many times". You can improve on it to support passing in parameters, etc suitable for your situation. Also take a look at RedirectToAction with support for additional parameters, if you want to pass parameters to the action method, this link will be useful.
public class UserActivityController : BaseController
{
//GET /UserActivity/Index
public ActionResult Index(int page = 1, string sort = "Id", string sortDir = "ASC", string filter = null)
{
// Your other implementation here. I have removed it for brevity.
if (userActivity != null)
{
return RedirectToAction("Index");
}
return View(userActivity);
}
public ActionResult Index()
{
return View();
}
}
I don't know what is the value of redirectUrl but I suppose it to be null. I also suppose that your userActivity is not null. So return RedirectToAction(returnUrl); gets called.
When you call RedirectToAction(null) you actually redirect to the same action and everything repeats again.
I also am wondering why would you need to return View(userActivity); when your userActivity is null. I suppose you have a logical error.
Lets say we have a 'Client' object:
(am just mentioning the attributes and the equals method alone of the 'Client' object below!!)
public class Client {
private Long clientId;
private String clientName;
private Integer status;
//getters and setters for above attributes
.....
...
//hashCode method
....
..
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Client other = (Client) obj;
if (clientId == null) {
if (other.clientId != null)
return false;
} else if (!clientId.equals(other.clientId))
return false;
if (clientName == null) {
if (other.clientName != null)
return false;
} else if (!clientName.equals(other.clientName))
return false;
if (status == null) {
if (other.status != null)
return false;
} else if (!status.equals(other.status))
return false;
return true;
}
}
From the above equals method itz clear that 'two' client objects are said to be equal if all the attributes of the two objects are identical.
Now assume a scenario where I need to compare two collections(named say incomingClients and existingClients) of Client objects.
The first collection(Collection incomingClients) was generated after reading the 'client' data from a csv/xls file.
The second collection(Collection existingClients) contains, all the existing clients currently in the system.
I can do the following code (using apache CollectionUtils)to get the 'common' clients.
Collection<Client> commonClients = (Collection<Client>)CollectionUtils.intersection(incomingClients,existingClients);
Now with the below code I can remove these commonClients from both the collections.
incomingClients.removeAll(commonClients);
existingClients.removeAll(commonClients);
The intention of removing the 'common clients objects' was that, we dont need to do 'any processing' for these records,
as we are really not at all interested in those records.
Now how can I figure out which are the entirely 'new clients' in the 'Collection incomingClients' collection?
(When I say 'new' it means a client having a new 'clientId' which doesnt exist in the 'Collection existingClients')
Also, how can I figure out which are the clients which needs 'modification'
(When I say 'modification' it means that the 'Collection incomingClients' and Collection existingClients'
have the same clientId, but, say, different 'clientName')
I know that we can do the normal 'for' loop('check below') to figure out the 'new'/'modification needed' clients.
But I thought of writing 'something new', whether we can achieve this using some classes/function in the 'Apache CollectionUtils' package.
Collection<Client> newClients = new ArrayList<Client>();
Collection<Client> toBeModifiedClients = new ArrayList<Client>();
boolean foundClient = false;
Client client = null;
for(Client incomingClient :incomingClients){
foundClient = false;
for(Client existingClient : existingClients){
if(existingClient.getClientId().equals(incomingClient.getClientId())){
client = existingClient;
foundClient = true;
break;
}
}
if(foundClient){
toBeModifiedClients.add(client);
}else{
//not found in existing. so this is completely new
newClients.add(incomingClient);
}
}
Am I 'complicating' a simple stuff??
Any thoughts??
First, yes, you are complicating "simple stuff". Your entire question could be summarized as follows:
Given collections A and B, how can I get the following using CollectionUtils:
A-B, using a particular function that determines equality
A∩B, using a particular function that determines equality
So, yes. CollectionUtils has what you need. Look at CollectionUtils.select().
I am new to ASP.NET MVC and I am stuck on a point. I am working on a classified site. My situation is, I have a lot of categories in which a user can post their ads and each ad category have different View. I have created a Controller Action like
public ActionResult PostAd(string CategoryName, string SubCategoryName)
{
if(categoryName == "Vehicle" && SubCategoryName == "Cars")
{
var model = new CarAdViewModel();
// set CarAdViewModel properties...
return View("CarAdCreateView", model);
}
else if(categoryName == "Vehicle" && SubCategoryName == "Bikes")
{
var model = new BikeAdViewModel();
// set BikeAdViewModel properties...
return View("BikeAdViewModel", model);
}
else if(categoryName == "Property" && SubCategoryName == "RentHouse")
{
var model = new RentHouseAdViewModel();
// set RentHouseAdViewModel properties...
return View("RentHouseAdViewModel", model);
}
else................... so on and so on
}
My problem is I have huge number of Categories and Sub Categories almost 60+. And if I keep on coding like above for 60+ categories and subcategories, my PostAd method is going to blast and become unmanageable.
Please tell me some best practice or pattern which can bring me out of this problem.
Unfortunately, some of what you are doing cannot be avoided. There needs to be some form of model and view selection based on category.
Use a factory pattern. Create a base class:
public abstract class BaseCategory
{
public abstract string GetViewName();
public abstract Object CreateModelFromFormData();
}
For each category, create a sub-class derived from BaseCategory and implement the abstract functions.
In your action, do the following:
public ActionResult PostAd(string categoryName, string subCategoryName)
{
BaseFactory factory;
if (categoryName == "Vehicle")
{
if (subCategoryName == "Cars")
{
factory = new CarsFactory();
}
else ...
}
else ...
return View(factory.GetViewName(), factory.CreateModelFromFormData());
}
I have a couple reasons for this schema:
I am purposefully using if/else for the factory selection. Your controller is going to be created and re-created for every action call. So pre-populating a list will constantly and needlessly create objects for categories that will not be selected. A simple if/else will be more efficient. If you want to prevent the if/else, you can put your factories in a Dictionary and select based on the categories, but that would be a lot of needless constructor actions.
I made the CreateModelFromFormData a function because I assume you'll need to copy data from the posted form data. This may require passing in data, but I left the function parameterless.
I used base/derived classes because the copying of the form data will probably need to be custom from the model being created and the form data being posted. Also, saving to persistent storage (file or database) may be category-specific as well.
It would be one of some possible solutions
public class PostAdData
{
public string CategoryName;
public string SubCategoryName;
public string ViewName;
public Type Model;
}
public class PostController : Controller
{
private readonly List<PostAdData> _theData;
public HomeController()
{
_theData = InitializeData();
}
public ActionResult PostAd(string categoryName, string subCategoryName)
{
var data = _theData.FirstOrDefault(c => c.CategoryName == categoryName && c.SubCategoryName == subCategoryName);
if (data != null)
{
var model = Activator.CreateInstance(data.Model);
return View(data.ViewName, model);
}
return View("Error");
}
[NonAction]
public List<PostAdData> InitializeData()
{
var result = new List<PostAdData>
{
new PostAdData
{
CategoryName = "Vehicle",
SubCategoryName = "Cars",
ViewName = "CarAdCreateView",
Model = typeof (CarAdViewModel)
}
};
return result;
}
}
You should make this data driven. You create a lookup table that has a compound primary key of category and subcategory. Then it has a table with View in it. Then you simply ad rows for each category/subcategory/view combination.
If you absolutely don't want a database, then you can use a simple hashset or dictionary.
var views = new Dictionary<Tuple<string,string>,string>();
views.Add(new Tuple<string,string>("Vehicle", "Cars"), "CarAdCreateView");
Then in your PostAd you just lookup the correct view.
What a beautiful solution on www.asp.net to my question, here is the link : http://forums.asp.net/t/1923868.aspx/1?ASP+NET+MVC+Conditional+ViewModel+Abstraction
Edit:
My code is :
public class AdsController : Controller
{
private readonly IAdService _adService;
public AdsController(IAdService adService)
{
_adService = adService;
}
public ActionResult PostAd(string Category, string SubCategory)
{
//Here I will call
var strategy = GetStrategy(CategoryName, SubCategoryName);
strategy.FillModel(_adService );
return View(strategy.ViewName, strategy.Model);
}
}
I have code like this:
//Fields
Product _prod, _existingProd;
void Test()
{
_prod = MakeAndPopulateSomeRandomProduct();
_existingProd = GetProdFromDb(1);
Mapper.CreateMap()
.AfterMap((s, d) =>
{
Console.WriteLine(d==_existingProd); //Why does this print false?
//Customize other properties on destination object
});
Mapper.Map(_prod, _existingProd);
}
When I call Test(), false is printed but I expected true. In my scenario, it is important to be able to access the original destination object via the AfterMap argument. I only included the fields to demonstrate the problem but in my real code, I don't have direct access to them. How can I access the object instances passed in to Map() when customizing the mapping?
The following example works. Probably you are using some type converter which creates new instance... Also please provide all mapping configurations to better understand the problem.
[TestFixture]
public class AfterMap_Test
{
//Fields
private Product _prod, _existingProd;
[Test]
public void Test()
{
Mapper.CreateMap<Product, Product>()
.AfterMap((s, d) =>
{
Trace.WriteLine(d == _existingProd); //Why does this print false?
//Customize other properties on destination object
});
_existingProd = new Product {P1 = "Destination"};
_prod = new Product {P1 = "Source"};
Mapper.Map(_prod, _existingProd);
}
}
internal class Product
{
public string P1 { get; set; }
}
I have a custom activity where the user selects a variable from a drop down list in a rehosted designer environment. My problem is I want to get variable's name along with its value.
Let's say my custom activity has "InArgument MyVar{ get; set; }".
I'm currently getting the variable's name by parsing "((Microsoft.VisualBasic.Activities.VisualBasicValue)MyVar.Expression).ExpressionText". Is there any better way?
You can't surelly know the true value of an argument until the runtime.
But, you can take it's default value, using those extension methods:
public static T GetImediateValueOrDefault<T>(Activity<T> activity, ModelItem modelItem)
{
if (activity == null)
return default(T);
var exprAsLiteral = activity as Literal<T>;
if (exprAsLiteral != null)
return exprAsLiteral.Value;
var exprAsVbValue = activity as Microsoft.VisualBasic.Activities.VisualBasicValue<string>;
if (exprAsVbValue != null)
return modelItem.GetDefaultValueOfScopedVariable<T>(exprAsVbValue.ExpressionText);
return default(T);
}
public static T GetDefaultValueOfScopedVariable<T>(this ModelItem modelItem, string variableName)
{
IEnumerable<ModelItem> scopedVariables = modelItem.GetScopedVariables();
ModelItem variableModelItem = scopedVariables.FirstOrDefault(v => Equals(variableName, v.Properties["Name"].ComputedValue));
if (variableModelItem == null)
throw new KeyNotFoundException();
Variable<T> variable = variableModelItem.GetCurrentValue() as Variable<T>;
if (variable == null)
return default(T);
return GetImediateValueOrDefault(variable.Default, modelItem);
}
Not guaranteed, use with caution