Pages 404 when created via the django-cms API - django-cms

I'm building a system which creates a site object for clients added to the system and off the back of that creates some basic pages for the site.
Pages are created with published=True however after browsing to them via pages admin or directly to URLs the result is a 404.
To fix the 404 I've noticed simply saving the page's settings then makes the pages load. I therefore suspect there's further actions which are triggered by the CMS admin, whether that be through the form used or signals.
The Code
The creation of pages comes from a post_save signal;
#receiver(post_save, sender=Client)
def create_site_on_client_creation(instance, **kwargs):
"""
When a Client is created, create a Site object with the client slug as a
sub-domain to the main sites base domain. Also update the site object
if the client slug changes.
"""
site = instance.site or Site()
if not instance.site_id:
instance.site = site
instance.save(update_fields=['site'])
create_initial_pages(
site, extra_pages=[
'Privacy Policy',
'Terms of Use'
]
)
The function then creating the content is;
def create_initial_pages(site, **kwargs):
""" Create the initial pages required for a site """
# Create a CMS "home" page on the initial site creation
created_root = False
root_page = Page.objects.on_site(site).drafts().filter(
title_set__language=settings.LANGUAGE_CODE,
title_set__title=HOME_TITLE,
is_home=True
).first()
if not root_page:
created_root = True
root_page = create_page(
title=HOME_TITLE,
template=TEMPLATE_INHERITANCE_MAGIC,
language=settings.LANGUAGE_CODE,
site=site,
published=True
)
if 'extra_pages' in kwargs:
for page_title in kwargs['extra_pages']:
# Check if we can find
existing = Page.objects.on_site(site).drafts().filter(
title_set__language=settings.LANGUAGE_CODE,
title_set__title=page_title,
).exists()
if not existing:
create_page(
title=page_title,
template=TEMPLATE_INHERITANCE_MAGIC,
language=settings.LANGUAGE_CODE,
site=site,
parent=root_page,
published=True
)
if created_root:
root_page.is_home = True
root_page.save()

I think what's happening here is that you are making changes to the root page after it is published which is therefore making changes to the draft:
if created_root:
root_page.is_home = True
root_page.save()
# You may need to publish here
root_page.publish(settings.LANGUAGE_CODE)
The API always fires the publish action on creation if the published kwarg is added: https://github.com/divio/django-cms/blob/develop/cms/api.py#L203
The tests are a good place to see how the api is used by code, finding a test that creates a page tree should help you to see if you have missed a specific setting that causes your page to not be seen: https://github.com/divio/django-cms/blob/develop/cms/tests/test_multilingual.py#L85
Also be sure that your new site has a language configuration in the settings file, it's possible that the admin is confused by the page and language combination and when you "save the page settings" you are actually creating an entry in your current language: http://docs.django-cms.org/en/latest/reference/configuration.html#internationalisation-and-localisation-i18n-and-l10n
I would advise rolling / commenting your code back to just the one root page and get that working as you expect, then when that is working you can expand upon it with more pages.

Try just calling the publish method (with a language) on the page after the call to create_page. You'll need to get the page from the create_page method. e.g...
my_page = create_page(
title=page_title,
template=TEMPLATE_INHERITANCE_MAGIC,
language=settings.LANGUAGE_CODE,
site=site,
parent=root_page,
published=True
)
my_page.publish("en")

Related

"Iteration ID" for a CustomVision Project (for use in MSFlow action)?

I'm building an MSFlow which sends a SharePoint pic lib pic to a just-trained CustomVision Classifier, which then sends back a label (eg "Green", "Red", etc);
Challenges:
My MSFlow "CustomVision" action is failing, stating "there's no default iteration for this project. please provide an Iteration ID"
There is nowhere on the CustomVision project's settings page which displays this IterationID !
How / where to find this iteration ID (appears to be a GUID) ???
Turns out the IterationID can be found as follows:
Browse to your custom vision projects page URL
(eg https://www.customvision.ai/projects)
=> browser will display a set of "tiles" - one for each of your existing projects;
Navigate (click) on your particular project for which you seek the IterationID;
=> browser will redirect to the "manage" page (note: defaults to Training Images page) for your project;
It will look something like this:
https://www.customvision.ai/projects/<project GUID here>#/manage
Navigate (click) on the Performance tab of this project
=> browser will direct to the "performance" page, something like this:
https://www.customvision.ai/projects/<project GUID here>#/performance
Note: all of the "iterations" (ie training iterations) will be tabbed along the left side
Select the (training) iteration you wish to use as the "web service" for actually classifiying incoming images;
=> browser will display details/metrics for that (training) iteration
Click on the "PredictionURL" tab in the upper left region of the page
=> a pop-up window will display all the settings-related data you'll need to consume the underlying web service ("API") wrapped around this classifier!
In particular, you'll see 2 different URLs:
For ImageURL-as-input:
https://southcentralus.api.cognitive.microsoft.com/customvision/v2.0/Prediction/<projectGUIDhere>/url?iterationId=g9fc4e82-3f95-4ec1-acf2-9b12bba2b409
For ImageFILE-as-input:
https://southcentralus.api.cognitive.microsoft.com/customvision/v2.0/Prediction/<projectGUIDhere>/image?iterationId=g9fc4e82-3f95-4ec1-acf2-9b12bba2b409
No matter which URL you inspect, you'll see the same value for IterationID - and there you have it!
Copy & paste this IterationID GUID into your MSFlow CustomVision Action, and it should work!
In the custom vision portal home, Select the project you are using, then select the Performance Tab. On the left side of the page you would see Iterations. Select the Iteration that you want and select Prediction URL. This will open a new dialog which gives the URL's for image URL and image file. In this URL the iteration id is a parameter that is passed, Copy the id and use it in your application.
If you choose any iteration as default the iteration id would not be required in the image URL.

How to make WordPress Rest API parameters accessible without authentication?

How can I make certain parameters of the WordPress Rest API accessible to anyone without first being authenticated – for example, the page parameter doesn't work (where blog is a custom post type) in this query:
mysite.com/wp-json/wp/v2/blog?page=2&per_page=20
I've seen that in the past it's been possible to make these params available, for instance :
add_filter( 'json_query_vars', function( $valid_vars ) {
$valid_vars[] = 'offset';
return $valid_vars;
});
Is there any way to do something similar with today's version of the API?
For anyone who has the same problem, I've solved it. The page parameter is actually publicly available, offset is the one you need authentication for.
The reason the API didn't paginate was because the request url didn't have the paged query string set. Every time I tried to add it with the params option of the WordPress Node API, it didn't work:
wpapi.getNews().params('paged', 'paged').perPage( perPage ).page( pageNumber ).then(data=>
It didn't work because the request url created by the API seemed to always put the page parameter before the paged one, which resulted in paged being ignored when the query actually runs.
So in the end, I created a custom query (bit of a hacked way to do it, but it worked) like so:
Register the route:
wpapi.getNews = wpapi.registerRoute('wp/v2', '/news/(?P<customQuery>)');
Usage:
wpapi.getNews().customQuery('?paged&per_page=20&page='+pageNumber).then(data =>
Using the above, you can build any query, in any order you want. This helped me get the correctly paginated result. Also, we see 'getNews' here because I registered a route for accessing my custom post type called news.

How to let a user choose different Facebook permissions in the same page?

Suppose you want to build a webpage with Facebook PHP SDK, where you want to allow the user to select the information Facebook will return to the server. I've came with the following code to allow an user to either choose from allowing Facebook to send only the basic profile or else to also send the pages managed by this user.
session_start();
// Load the Facebook PHP SDK
require_once __DIR__ . '/facebook-sdk-v5/autoload.php';
define('APP_ID', 'xxxxxxxxxxx');
define('APP_SECRET', 'xxxxxxxxxxxxxxxxxxxx');
$fbProfile = new Facebook\Facebook([
'app_id' => APP_ID,
'app_secret' => APP_SECRET,
'default_graph_version' => 'v2.7'
]);
$fbPages = new Facebook\Facebook([
'app_id' => APP_ID,
'app_secret' => APP_SECRET,
'default_graph_version' => 'v2.7'
]);
$helperProfile = $fbProfile->getRedirectLoginHelper();
$redirectUrlProfile = 'http://www.example.com/link1.php';
$loginUrlProfile = $helperProfile->getLoginUrl($redirectUrlProfile);
echo 'Get profile with Facebook!<br>';
$helperPages = $fbPages->getRedirectLoginHelper();
$permissions = ['pages_show_list']; // Optional permissions
$redirectUrlPages = "http://www.example.com/link2.php";
$loginUrlPages = $helperPages->getLoginUrl($redirectUrlPages, $permissions);
echo 'Get pages with Facebook!';
If I use the above code (commenting the non-relevant parts) with only one facebook object to either retrieve the profile or the pages managed by user (but not both), everything works fine. But if I use both objects concurrently to give a choice to the user, I get a FacebookSDKException. I guess this is due to CRSF cookies.
Is there any way to circunvent this problem?
I guess this is due to CRSF cookies.
Correct. Calling the getLoginUrl method creates a new random state value and writes it into the session, overwriting any previously stored one.
So if you call the method twice (or more times), login will only work if you call the login dialog via the last login URL created, because only that contains a state value that matches the one stored in the session.
If you want to keep using two different redirect URIs, then you need to implement an additional step to create the correct login URL, and only that one.
So you have two links in your page, both pointing to a script on your server, and passing what permissions to ask for via a GET parameter (whether you want to pass permission names directly, or just a flag like ?loginmode=1/?loginmode=2, is up to you.)
In that script, you decide which redirect URI and scope value to call the getLoginUrl method with - once. And then your script just redirects to that login URL.
(But keep in mind that the step that exchanges the code for an access token also requires the redirect URI parameter to be passed, again - and again with the exact same value that was used in the login dialog call.)
So doing it the way #luschn suggested in comments, using the JS SDK for login purposes, is probably much easier. FB.login can be called with different scopes from different points in your client-side JS code without any such problems.

Query CMSPlugin model for draft or live objects

I've got a CMSPlugin & a request to display it's items in another app.
Obviously for every plugin you create there is a live and draft version, so doing audio = Audio.objects.all() brings you duplicate instances.
How would you go about creating a query which only returns the plugin objects from public pages?
My plugin;
class Audio(CMSPlugin):
"""
Model for storing audio clips.
"""
caption = models.CharField(
_("Title"),
max_length=255,
blank=True
)
audio_track = models.FileField()
description = models.CharField(
_("Description"),
max_length=255,
blank=True,
null=True
)
How about something like this:
Audio.objects.filter(placeholder__page__publisher_is_draft=False)
This assumes all Audio plugins belongs to a CMS page. CMSPlugins are not guaranteed to have a page associated with them! Unless you set page_only option to True:
page_only = True
Docs: http://django-cms.readthedocs.org/en/latest/reference/plugins.html#page-only

How do I disable local role acquisition after a workflow transition?

Using the Intranet workflow, and when the user selects the hide transition to move the content to the private state, I want to disable local role acquisition. I think I'll want to re-enable when they move it back to internal draft.
I can see how this is done by the sharing view within plone.app.workflow. It has a method, update_inherit, that sets context.__ac_local_roles_block__ to either True or None.
How might I do something similar with a transition after_script?
Plone 4.1.5
plone.app.workflow 2.0.6
More info on my use case:
We have two users, contributor_1 and contributor_2, possessing only the Member role globally. At the site root, create a folder and assign the two users local roles to add, edit, and view.
Here is the default private state/manage_permissions:
If contributor_1 creates a page and tries to hide it, contributor_2 can still view, edit, and transition it back to internal draft because contributor_2 is inheriting those local roles.
Here is what I first tried:
While this does effectively make the page private to only contributor_1 and any Manager, contributor_1 cannot share access with contributor_2 while the item is in the private state. No private collaboration. UPDATE: contributor_1 can manipulate the sharing view (ie. they have access to delegate local roles), but delegating those local roles (Contributor, Editor, Reader) does not translate to their respective access since I have revoked the role-permission mapping in the above workflow.
Now, if set back to the default permission map, users can achieve the private state they want by hiding the content item and then going to the sharing tab and disabling local role acquisition. The problem is they don't want to have to remember to click the checkbox on the sharing tab every time they want to hide something.
As SteveM already commented, the workflow tool can handle this for you.
You need need to manage all the necessary permission with your workflow, this depends on your needs.
UPDATE: You workflow has to manage the "Delegate Roles XYZ [Reader/Contributor/etc.]" and "Change local roles" permissions. This way the user "contributor_1" can delegate roles in the "Private" area.
Do NOT acquire any permission settings on your hidden state.
In your case the Owner (probably the manager too) needs some permissions to view/edit/changestate.
Another hint for defining workflows.
Check this package it generally manages all the permissions in action groups, like View (View, Access content permissions, etc) or Add (Add Folders, Add portal content, Add..., etc.) All Plone default permissions are mapped to the right action group, and it let's you create a workflow within 5 minutes. The best part is, you can write the workflow in human readable specification file (custom DSL).
I created a utils.py module within my package containing one method: a (nearly) straight copypasta of the update_inherit method the sharing view uses. Then, I imported utils in my package's __init__.py.
from AccessControl import Unauthorized
from AccessControl.SecurityInfo import ModuleSecurityInfo
from Acquisition import aq_base
from Products.CMFCore import permissions
from Products.CMFCore.utils import getToolByName
security = ModuleSecurityInfo( 'united.policy.utils' )
security.declarePublic('update_inherit')
def update_inherit(context, status=True, reindex=True):
"""Enable or disable local role acquisition on the context.
Returns True if changes were made, or False if the new settings are the
same as the existing settings.
"""
portal_membership = getToolByName(context, 'portal_membership')
if not portal_membership.checkPermission(permissions.ModifyPortalContent,
context):
raise Unauthorized
block = not status
oldblock = bool(getattr(aq_base(context), '__ac_local_roles_block__',
False))
if block == oldblock:
return False
if block:
# If user has inherited local roles and removes inheritance, locally
# set roles he inherited before to avoid definitive loss of access
# (refs #11945)
user = portal_membership.getAuthenticatedMember()
context_roles = user.getRolesInContext(context)
global_roles = user.getRoles()
local_roles = [r for r in context_roles if r not in global_roles]
if local_roles:
context.manage_setLocalRoles(user.getId(), local_roles)
context.__ac_local_roles_block__ = block and True or None
if reindex:
context.reindexObjectSecurity()
return True
Then, I created two scripts within my ./profiles/default/workflows/intranet_workflow/scripts/ directory bound to their respective transitions:
after_hide.py
## Script (Python) "after_hide"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=review_state
##title=
##
from united.policy.utils import update_inherit
update_inherit(review_state.object, False)
after_show.py
## Script (Python) "after_show"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=review_state
##title=
##
from united.policy.utils import update_inherit
update_inherit(review_state.object, True)

Resources