2sxc Entity with a field that must be unique - 2sxc

A user create a Post (Entity) and define a UrlKey field (input type url-path)
I want prevent a duplicate value (if another entity of this type already had a urlkey with same content)
Is there a way to accomplish that ?
* Edited *
Looking to the 2sxc code I didnt found a simple way to do that.
One thing that I have in mind is to create a ApiController/Endpoint that I can call and make the validation that I want, but for this I need to change the view from the Edit Content (for the user, not the admin one).
I found to the save in /dist/ng-edit/main.js that is minified, there I could change and call my controller/endpoint, but to change to show some feedback messages to user and after call the original endpoint is difficult with minified file.
Is a possibility to have the code that generate the main.js ? (Maybe is already there, and I couldnĀ“t found)

I am not aware of a built in (easy) way to accomplish this. But doing it in the View with a warning is probably a good fallback option. Assuming you have a View (ListContent) that shows a group of Posts, you could add something like this, though it is catching it AFTER the fact...
I just did something like this a few weeks ago because a client kept repeating Titles in the Blueimp Gallery app. So you can just drop this in to _AlbumList Bootstrap.cshtml at line 4, create a few duplicates (Titles which will generate duplicate Paths) and you should see an option to fix them... So, after the fact, if there are duplicates and the current user is Super/Admin/ContentManager, then let them know the situation and make it easy to fix:
#using DotNetNuke.Entities.Portals #* used by IsAdminOrHost() *#
#using DotNetNuke.Entities.Users #* used by IsAdminOrHost(), IsContentManager() *#
#{
if(IsContentManager(Dnn.User)){
var query = AsDynamic(Data["Default"])
.GroupBy(x => x.Path)
.Where(g => g.Count() > 1)
.Select(y => y.Key)
.ToList();
if(query != null) {
<pre>
You have duplicates (#query.Count()), please fix them:
#foreach(var dupe in AsDynamic(Data["Default"])
.Where(p => query.Any(x => x == p.Path)) ) {
<span #Edit.TagToolbar(dupe)>-- #dupe.EntityId, #dupe.Path</span>
}
</pre>
}
}
}
#functions {
// is the current user an Admin or Host?
public bool IsAdminOrHost(UserInfo userInfo) {
return userInfo.IsSuperUser || userInfo.IsInRole(PortalSettings.Current.AdministratorRoleName);
}
// is the current user a Content or Site Manager (or Admin+)?
public bool IsContentManager(UserInfo userInfo) {
return userInfo.IsInRole("Content Managers")
|| userInfo.IsInRole("Site Managers")
|| IsAdminOrHost(userInfo);
}
}
Results should look something like this:

Related

How to override Doctrine #userAware annotion / filter on specific query?

I follow this instruction to create a #userAware annotation which automatically limits all queries on specific entities to the current user.
For example $todoRepo->findByTag("someTag") will automatically add WHERE user_id = 123 to the resulting query to make sure that only ToDo entities of the current user (id 123) can be accessed.
This works great but has one big downside: If for example the admin whats to know how many ToDo entities use the tag someTag he will only find his own entities...
Since using an #userAware annotation seems to be quite common, I wonder if there is any best practice on how to disable/bypass/override this filter/annotation on a specific query.
You can disable the filter. In your example, you must change the kernel request event, like that ...
public function onKernelRequest()
{
if ($user = $this->getUser()) {
if ($user->isAdmin() /* or whatever */) {
$this->em->getFilters()->disable('user_filter');
} else {
$filter = $this->em->getFilters()->enable('user_filter');
$filter->setParameter('id', $user->getId());
$filter->setAnnotationReader($this->reader);
}
}
}

Entity Framework paging and sorting is giving unexpected results

I'm currently building a ASP.NET web app search result page. I've implemented different types of sorting for users but some of them are giving unexpected results.
These two line of code are executed regardless of the sort type or any other factors:
var resources = ctx.Resource.Where(p => p.CityAlias == city.ToLower() && p.Company.Hidden != true && p.State == 1);
FilterResources(ref resources, resourceTypeId, serviceId);
FilterResources:
private void FilterResources(ref IQueryable<Resource> allRes, int resourceType, int selectedService)
{
allRes = allRes.Where(p => p.ResourceType_Id == resourceType && p.ResourceService.Any(x => x.Service_Id == selectedService));
}
Resource.ResourceService is of type ICollection that is mapped to a database table of the same name with a foreign key pointing to Resource.Id
So far so good. First a working sort example:
private void SortResourcesByName(ref IQueryable<Resource> resources)
{
resources = resources.OrderBy(p => p.Name);
}
Then we handle paging:
int count = resources.Count();
var resourceList = resources.Skip((page - 1) * 10).Take(10).ToList();
This set from entities seems to be correct and in the right order, but here's when things get weird:
If we use a different sort, some of the pages have duplicate results and in some cases if I go through the pages, I can see duplicate (or more) Resources scattered across the list.
For example:
private void SortResourcesByPrice(ref IQueryable<Resource> resources, int serviceId)
{
resources = resources.OrderByDescending(p => p.ResourceService.FirstOrDefault(s => s.Service_Id == serviceId).Price.Value)
.ThenByDescending(p => p.Company.CompanyService.FirstOrDefault(c => c.ServiceId == serviceId).Price.Value);
}
If I use this sort, the first page of the view is correct, but when I start navigating to other pages, I start to see duplicate results.
The query returns just over 200 results, which are divided among 21 pages. Pages 3 and 4 are exactly the same for some reason, pages 5 and 6 also yield same results. In these cases, ResourceService.Price = null.
Bonus weirdness:
using OrderBy() instead of OrderByDescending does not give duplicate results
private void SortResourcesByPrice(ref IQueryable<Resource> resources, int serviceId)
{
resources = resources.OrderBy(p => p.ResourceService.FirstOrDefault(s => s.Service_Id == serviceId).Price.Value)
.ThenBy(p => p.Company.CompanyService.FirstOrDefault(c => c.ServiceId == serviceId).Price.Value);
}
I'm at a total loss here, so I'd really appreciate help. If you can't make sense of my example code or can't understand the question in some other way, I'd happily try to provide more information.
Edit:
I'd like to add that the amount of results/Resources is always the same, regardless of what sort I'm using (just as it's supposed to).
Edit 2:
Fixed some numbers & typos
You can use https://expressprofiler.codeplex.com/ to view the SQL generate by EF copy and paste the sql query directly in SQL Server Management studio and look where is the duplicate origine, you can change query to remove duplicate entry and after search Linq responsible.
You can put the SQL here too

How to pass a fresh _id from a method insert into a subscription/publication?

I have an app where you can choose (or add if they don't exist!) a superhero/villain character from a certain universe on the first page; then outfit him with weapons, clothes, and gadgets on the second page (build).
I have this route defined:
Router.route('/build/:character', {
name: 'build'
waitOn: Meteor.subscribe('characters', {name: this.params.character})
//and a few other subscriptions and sessions as well for the items
//and stuff, but those don't matter here.
}
The link from the specific character, though, passes along a query as well:
<a href="{{pathFor 'build' query=this.universe}}">
So the final link could look something like this:
/build/Aquaman?DCComics
Now the page you are on will display a list of weapons and gadgets where you could also add other stuff if you so wish. Then you are supposed to drag the items you want to include onto your version of this hero.
Problem is, at this point the app doesn't know you even want to create your own hero. Maybe the user is just looking through them for fun. There's a button that the user has to click first to initialize the creating process, and that's when the actual _id is created, something like this:
Meteor.methods({
buildHero: function(heroCharacterName, heroUniverse) {
var heroToAdd = {}
heroToAdd['characterName'] = heroCharacterName
heroToAdd['universe'] = heroUniverse
heroToAdd['_createdAt'] = new Date()
CreatedHeroes.insert(heroToAdd, function() {
if (! error)
//Update the subscription somehow...
})
}
})
So, the _id that is created here in the new Collection must be passed along to a subscription somehow, because I don't want the user to see other personal heroes that have been created, only his own newly created one.
The solution I have in mind is adding the _id onto the URL in form of a hastag, and use this.params.hash in the subscription like so:
Router.route('/build/:character', {
name: 'build'
waitOn: [Meteor.subscribe('characters', {name: this.params.character}),
Meteor.subscribe('createdheroes', this.params.hash)]
}
First of all, is this a valid approach? If so, how do I accomplish it; how do I actually update the URL to include this hash?
If not, what would be a better approach?
I think you have to handle this logic in the data context or in a template helper and not in the way of subscribing/publishing.
If I was you I would besure that the newly created item is being published and subscribed by the client and modify your search query just that it only adds the newly created item.
I am not sure if I understand your question well but what I got, you will know the last _id which was used on your insert.
Instead of letting done this automatically by meteor, just use the meteor method to create / get that _id value >> see Meteor Documentation
var new_id = new Mongo.ObjectID()
col1.insert({ _id: new_id, ... });
col2.insert({ ..., ref_col1_id: new_id, ... });

How do I add or remove collection document fields server side in Meteor?

I need to add or remove fields to a doc before insert or update in the allow or deny methods. I had presumed that the transform function would provide the needed functionality.
The meteor docs state
"An optional transformation function. Documents will be passed through
this function before being returned from fetch or findOne, and before
being passed to callbacks of observe, allow, and deny."
Whenever I tried to transform and return the doc from the function either from allow or deny the transformed version of the document was not what was inserted into the mongodb. I tried transforming via 2 strategies.
Strategy 1
var ts = new Date();
return _.extend(_.pick(doc, 'name', 'discounts', 'locations', 'url_map', 'client_updated_td', '_id'), { created_td:
ts, updated_td: ts, });
Strategy 2
// Discountsroutings.fields is in /lib/Discountroutings.js
Discountsroutings.fields = ['_id', 'created_td', 'updated_td', 'client_updated_td', 'name', 'discounts', 'locations', 'url_map'];
// this is in /server/discountsroutings.js var ts = new Date();
doc.created_td = ts; doc.updated_td = ts; return _.each(doc,function(value, key, list){
if(Discountsroutings.fields.indexOf(key) == -1 ){
delete doc[key];
}
});
Neither worked. In both cases fields were not removed though fields were added.
Interestingly, I tried the same two strategies from inside an insert allow and an insert deny and only Strategy #2 worked. So, for now I am just using Strategy #2 inside Deny insert/update methods. Works fine and isn't that difficult to wire up.
Am I doing this correctly? I want to add or remove fields from a collection server side the correct way.
Steeve have you tried my collection-hooks package? Sounds like what you need
you seem to know the list of fields you want to remove. So why don't you use $set and $unset to add and remove fields?
Recently needed to do the same thing and found no example here... thought I'd share how I did it:
Using
https://atmospherejs.com/matb33/collection-hooks
http://arasatasaygin.github.io/is.js/
Prospects.before.update(function (userId, doc, fieldNames, modifier, options) {
//check existence of other segment property and make sure to delete it if segment is updated from 'Other...' to something else
if (is.existy(doc.other_segment)) {
var segment = Segments.findOne({_id: modifier.$set.segment});
if (is.not.undefined(segment) && is.not.empty(segment)) {
if (is.not.equal(segment.name, 'Other...')) {
Prospects.update( {_id: doc._id} , {$unset: { other_segment : '' } } );
}
}
}});
Hope this helps! :)

Automatically refresh Drupal node after hook_view

I'm trying to show updated results for a CCK Computed Field.
The computation is based on fields in another node, so are not being automatically updated.
So: I'm calling node_save($node) in hook_view, which does make the adjustment but the results don't show until I refresh the page.
Is there a way to refresh the page automatically, or should I be approaching this from a different angle?
Edit: In response to Henrik's questions, here's more detail:
The hook_view and its node_save are below, the rest of the code is in a Computed Field in the 'project' content type, summing up values from another node. Without the node_save, I have to edit and save the 'project' node to get the result. With it, I just need to refresh the page.
Adding drupal_goto(drupal_get_destination()) in the hook_view gives a 'page not found', rather than the vicious loop I was expecting. Is there another place I could put it?
function mymodule_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
switch ($op) {
case 'view':
if($node->type == 'project') {
project_view($node);
break;
}
}
}
function project_view($node) {
node_save($node);
return $node;
}
Edit 1: Given the newly posted code and additional explanations, I have three suggestions that might solve the problem without redirecting:
As project_view() does not take the node argument by reference, you might want to actually grab its (potentially updated) result in mymodule_nodeapi by writing
$node = project_view($node);
instead of just
project_view($node);
If that works, it should also work without the indirection via project_view() by just calling node_save($node) directly in mymodule_nodeapi. (node_save() takes the node argument by reference).
AFAIK, computed fields basically provide two working modes that you can switch via checkbox on the field configuration form:
Computing the field once on node_save(), storing the result in the database, updating only on new save operations.
Not storing the field at all, instead recomputing it every time the node is viewed.
Have you tried the 'always recompute' option already?
Edit 2: My original answer was flawed in two ways at once, as it used a completely wrong function to retrieve the current request URI and did not check for recursion (as lazysoundsystem pointed out very courteously ;)
So the following has been updated to an actually tested version of doing the redirection:
Is there a way to refresh the page
automatically
You could try:
if (!$_REQUEST['stop_redirect']) {
drupal_goto(request_uri(), array('stop_redirect' => true));
}
This will cause Drupal to send a redirect header to the client, causing a new request of the current page, making sure not to redirect again immediately.
If the value is only ever going to be computed, you could just add something to your node at load time.
function mymodule_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
switch ($op) {
case 'load':
if($node->type == 'project') {
$node->content['myfield'] = array('#value' => mymodule_calculate_value(), '#weight' => 4, '#theme' => 'my_theme');
}
break;
}
}
}

Resources