Programmatically creating permissions for pages and collections in Wagtail - wagtail

I'm wondering if anyone has examples of how to create permissions in Wagtail for collections and pages and then give specific users access to that role. I see how to create the Collections and AuthGroups easily, but I don't see how to specify the AuthGroup permissions in detail.
Edit:
In my case, the answer below in addition, to this bit of code for module permissions allowed for everything to be automatically added for a group:
try:
perm = course_group.permissions.get(codename=x.codename, content_type=x.content_type)
except Permission.DoesNotExist:
perm_created = course_group.permissions.add(x)

I have some code like this in a population script. In this example, I want a group named Team to be able to add, change and choose wagtail images and documents in any collection. I also want them to be able to add, edit and publish any pages on the site. This example can hopefully be modified to suit another setup.
from wagtail.models import Page
from wagtail.core.models import Collection, GroupCollectionPermission, GroupPagePermission
from django.contrib.auth.models import Group, Permission
team_group, created = Group.objects.get_or_create(name='Team')
if created:
#This is only done once, when the group didn't already exist
root_collection = Collection.get_first_root_node()
GroupCollectionPermission.objects.create(group=team_group, collection=root_collection, permission=Permission.objects.get(content_type__app_label='wagtailimages', codename='add_image'))
GroupCollectionPermission.objects.create(group=team_group, collection=root_collection, permission=Permission.objects.get(content_type__app_label='wagtailimages', codename='change_image'))
GroupCollectionPermission.objects.create(group=team_group, collection=root_collection, permission=Permission.objects.get(content_type__app_label='wagtailimages', codename='choose_image'))
GroupCollectionPermission.objects.create(group=team_group, collection=root_collection, permission=Permission.objects.get(content_type__app_label='wagtaildocs', codename='add_document'))
GroupCollectionPermission.objects.create(group=team_group, collection=root_collection, permission=Permission.objects.get(content_type__app_label='wagtaildocs', codename='change_document'))
GroupCollectionPermission.objects.create(group=team_group, collection=root_collection, permission=Permission.objects.get(content_type__app_label='wagtaildocs', codename='choose_document'))
root_page = Page.objects.get(id=1)
GroupPagePermission.objects.create(group=team_group, page=root_page, permission_type='add')
GroupPagePermission.objects.create(group=team_group, page=root_page, permission_type='edit')
GroupPagePermission.objects.create(group=team_group, page=root_page, permission_type='publish')
To find the available wagtail content types and list their identifiers, I ran
for x in Permission.objects.order_by().values('content_type__app_label').distinct():
print(x['content_type__app_label'])
To find the permission codenames of a given content type, I ran
for x in Permission.objects.filter(content_type__app_label='wagtailimages'):
print(x.codename)
For the GroupPagePermission parameter permission_type, I found the options in the wagtail source. It lists these options:
PAGE_PERMISSION_TYPES = [
('add', _("Add"), _("Add/edit pages you own")),
('edit', _("Edit"), _("Edit any page")),
('publish', _("Publish"), _("Publish any page")),
('bulk_delete', _("Bulk delete"), _("Delete pages with children")),
('lock', _("Lock"), _("Lock/unlock pages you've locked")),
('unlock', _("Unlock"), _("Unlock any page")),
]
In my project I'm not adding users to groups programmatically, but hopefully this answer helps

Related

Programatically add a page to a known parent

I would like to create programatically a sub page for a known parent. How can I do that? The page creation will takes place in a signal receiver: the page is created on publication of another page.
Add a revision as well.
from wagtail.wagtailcore.models import Page
from models import MyPage
home = Page.objects.get(id=3) # or better Page query
my_page = MyPage(title="test", body="<h1>the body</h1>")
home.add_child(instance=my_page)
# later when a cms user updates the page manually
# there will be no first revision to compare against unless
# you add a page revision also programmatically.
my_page.save_revision().publish()
You can see how wagtail does this in the wagtailadmin pages create view (line 156).
https://github.com/wagtail/wagtail/blob/stable/1.13.x/wagtail/wagtailadmin/views/pages.py
Update 2018-09-18:
I built a 700 page site including 200 generated pages. I never added an initial Revision anywhere and no editors complained. After the first manual edit there will be a Revision. Go ahead and add an initial Revision if you think it is needed for traceability.
To create a page programmatically:
page = SomePageType(title="My new page", body="<p>Hello world</p>") # adjust fields to match your page type
parent_page.add_child(instance=page)
Below is my complete code to create a multi language page structure programatically. It will replace the "Wagtail Welcome Page" with a LanguageRedirectionPage instance.
More information about multi language pages:
Wagtail Docs - Internationalization
The page structure is as follows:
Page
LanguageRedirectionPage (will redirect to /en)
Page (en)
Page (de)
Page (fr)
where the created Site instance at the end of the code points to the LanguageRedirectionPage instance. This is the entry point of our application.
# Deletes existing pages and sites
Site.objects.all().delete()
Page.objects.filter(pk=2).delete() # Deletes Wagtail welcome page
root_page = Page.objects.filter(pk=1).get()
# Adds a LanguageRedirectionPage as a child of the Root Page
app_name = '[Your Project Name]'
page_slug = app_name.lower().replace(" ", "")
sub_root_page = LanguageRedirectionPage(
title=app_name,
draft_title=app_name,
slug=page_slug,
live=True,
owner=account,
)
root_page.add_child(instance=sub_root_page)
sub_root_page.save_revision().publish()
# Adds some language pages
for code,caption in dict(settings.LANGUAGES).items():
print(code, caption)
sub_root_page.add_child(instance=Page(
title=caption,
slug=code,
live=True,
owner=account,
))
# Adds a new Site instance (See Settings -> Sites in your Wagtail admin panel)
Site.objects.create(
hostname='localhost',
port='80',
site_name=app_name,
root_page=sub_root_page,
is_default_site=True,
)

How do you check in code if a request matches an EPiServer Visitor Group

We've set up a new "visitor group" in EPiServer 6r2, and we want to add a css class to the <body> tag of the site if the user is in that group, so different groups get different site designs. I'm trying to find out if the current visitor is in a matching group in the code-behind of a masterpage file in order to add this extra class and can't get the below code to return anything but false.
I'm not sure if the role name mentioned is the name you enter in the CMS UI when adding a visitor group.
Paul Smith blogged a proposed solution to this but I haven't been able to get it to return anything but false yet, and judging by the only comment on the blog article I'm not alone. Code sample #1 from this link (which is the one I'm using):
using EPiServer.Personalization.VisitorGroups;
...
bool match = EPiServer.Security.PrincipalInfo.CurrentPrincipal
.IsInRole("My Visitor Group", SecurityEntityType.VisitorGroup);
I found the developer guide to membership and role providers which states that replacePrincipal must be set to true for the correct principal to be in place. I checked and this is already the case for my config.
Documentation
EPiServer 7 doc
IPrincipal.IsInRole() extension
SecurityEntityType enum
Oddly I searched the 6r2 documentation from http://sdk.episerver.com/ and can't find the documentation for IPrincipalExtensions at all, even though I see the class in object browser in 6.2. in my sln. Details: Assembly EPiServer.ApplicationModules - C:\Windows\assembly\GAC_MSIL\EPiServer.ApplicationModules\6.2.267.1__8fe83dea738b45b7\EPiServer.ApplicationModules.dll - public static bool IsInRole(this System.Security.Principal.IPrincipal principal, string role, EPiServer.Security.SecurityEntityType type)
Member of EPiServer.Personalization.VisitorGroups.IPrinicipalExtensions
Please comment if you spot errors or I've missed anything as coding for EPiServer is a bit of a fog-of-war affair and I'm a little battle-weary.
Found it by browsing the object model and guessing. So much for documentation.
using EPiServer.Personalization.VisitorGroups;
using EPiServer.Security;
const string visitorGroupName = "Some users";
var groupHelper = new VisitorGroupHelper();
bool isPrincipalInGroup = groupHelper.IsPrincipalInGroup(
PrincipalInfo.CurrentPrincipal, visitorGroupName);
Tested and working in EPiServer 6r2 (aka 6.1).
String visitorGroupName must match the string entered into the "Name" box on the EPiServer admin interface when creating / editing the visitor group. See screenshot below:

How do I import Active Directory users into JIRA only from specific groups?

A caveat to begin with - I don't actually know if what I want to do is possible, particularly because I'm not well versed with LDAP/Active Directory or JIRA.
I'm trying to integrate my shiny new installation of JIRA with my existing active directory. What I want to do is set up some specific JIRA groups (e.g. in London\Security Groups\JIRA*) and then have JIRA only import the users who have membership of those groups. However, in the directory set up page in JIRA, I don't understand how to do this. It seems to indicate that I can import users and groups, but not users from groups.
What am I missing? (apart from expert level knowledge of AD!)
Update
Under my domain, I have an organisational structure like this:
London\Users
London\Security Groups\JIRA
Under the latter organisational unit, I have a security group called "jira-users". The former contains all users.
So far I've tried the following queries and none of them have worked :
(all prefixed with &(objectCategory=Person)(sAMAccountName=*)")
memberof=CN=jira-users,ou=London,ou=Security Groups,ou=JIRA,dc=mycompany,dc=local
memberof=CN=JIRA,ou=London,ou=Security Groups,dc=mycompany,dc=local
(prefixed with just &(objectCategory=Person)")
memberof=CN=jira-users,ou=London,ou=Security Groups,ou=JIRA,dc=mycompany,dc=local
Completed
The query that works is this :
memberof=CN=jira-users,OU=JIRA,OU=Security Groups,OU=London,DC=mycompany,DC=local
I hadn't realised that for a folder structure that is logically, left to right, London\Security Groups\JIRA, the organisational units need to be listed in reverse order.
Further Update
This only works when using the DirectorySearcher class for some reason, e.g.
DirectoryEntry rootEntry = new DirectoryEntry("LDAP://dc=mycompany,dc=local");
DirectorySearcher srch = new DirectorySearcher(rootEntry);
srch.SearchScope = SearchScope.Subtree;
srch.Filter = "(&(objectCategory=Person)(sAMAccountName=*)(memberof=CN=jira-users,ou=London,ou=Security Groups,ou=JIRA,dc=mycompany,dc=local))";
SearchResultCollection results = srch.FindAll();
This doesn't work in the LDAP explorer tool and subsequently, not in JIRA itself.
Last Update
So...for JIRA, you need to reverse the order AND remove the wildcard. Working query in the end is :
(&(objectCategory=Person)(memberof=CN=jira-users,OU=JIRA,OU=Security Groups,OU=London,DC=mycomapny,DC=local))
When you are setting up the user directory look under the User Schema settings. You should see a "User Object Filter" field. In there you should be able to add something like this:
(memberOf=cn=jira-users,ou=London,dc=mydomain,dc=com)
This will allow you to filter based on a specific LDAP group. Of course you will need to edit the values above to reflect your own environment.

What approach is best for mapping a legacy application tables named after years in Django?

Better see what the table names look like:
2009_articles
2010_articles
2011_articles
2009_customers
2010_customers
2011_customers
2009_invoices
2010_invoices
2011_invoices
Developers have simulated some kind of partitioning (long before mysql supported it) but now it breaks any try to make a quick frontend so customers can see their invoices and switch years.
After a couple on months I have the following results:
Changing Invoice._meta.db_table is useless cause any other relation deduced by the ORM will be wrong
models.py cannot get request variables
Option a:
Use abstract models so Invoice10 adds meta.db_table=2010 and inherits from Invoice model, and Invoice11 adds meta.db_table=2011, Not DRY although the app shouldn't need to support more than two or three years at the same time, but I will have to still check if
Option b:
Duplicate models and change imports on my views:
if year == 2010:
from models import Article10 as Article
and so on
Option c:
Dynamic models as referred to in several places on the net, but why have a 100% dynamic model when I just need a 1% part of the model dynamic?
Option d:
Wow, just going crazy after frustration. What about multiple database settings and use a router?
Any help will be much appreciated.
Option e: create new relevant models/database structure, and do an import of the old data in the new structure.
Ugly, but must inform.
I achieved something useful by using Django signal: class_prepared and ThreadLocal to get session:
from django.db.models.signals import class_prepared
from myapp.middlewares import ThreadLocal
apps = ('oneapp', 'otherapp',)
def add_table_prefix(sender, *args, **kwargs):
if sender._meta.app_label in apps:
request = ThreadLocal.get_current_request()
try:
year = request.session['current_year']
except:
year = settings.CURRENT_YEAR
prefix = getattr(settings, 'TABLE_PREFIX', '')
sender._meta.db_table = ('%s_%s_%s_%s') % (prefix, year, sender._meta.coolgest_prefix, sender._meta.db_table)
class_prepared.connect(add_table_prefix)
So is one model class mapping several identical database tables (invoices_01_2013, invoices_02_2013, ...) depending of what month and year the application user is browsing.
Working fine in production.

Google App Engine, How to automatically load model class for ReferenceProperty

I have modular project structure, like this:
./main.py
./app.yaml
../articles
.../__init__.py
.../models.py
../blog
.../__init__.py
.../models.py
../comments
.../__init__.py
.../models.py
I have defined models in file models.py for each package (this is application). I have defined next models for "comments" application:
class Comment(db.Model):
author = db.UserProperty(auto_current_user_add=True)
title = db.StringProperty(default="Title")
text = db.TextProperty("Message", default="Your message")
# references to any model
object = db.ReferenceProperty()
and in "articles" application I have defined next models:
class Article(db.Model):
author = db.UserProperty(auto_current_user_add=True)
title = db.StringProperty(default="Title")
text = db.TextProperty("Message", default="Your message")
1) On first loading of page - I create new article:
from articles.models import Article
article = Article(title="First article", text="This is first article")
article.put()
2) On second loading of page I create new comment:
from articles.models import Article
from comments.models import Comment
article = Article.get_by_id(1)
comment = Comment(title="First comment", text="This is first comment")
comment.put()
3) On thind loading of page, I want to see all comments for all articles, blogs, and other objects in whole datastore:
from comments.models import Comment
comments = Commen.all()
for comment in comments:
# print comment and article title
print "%s: %s" % (comment.title, comment.object.title)
Actual result: "KindError: No implementation for kind 'Article'"
Expected result: automatically detect object type for reference and load this class
See more on: http://code.google.com/p/appengine-framework/issues/detail?id=17
Project need your help!
In order to be able to return entities of a given kind, App Engine has to be able to find the Model class for it. There's no mechanism built in for doing so, because all it has to look it up with is the entity kind, which can be any arbitrary string.
Instead, import the modules containing the models you may reference from the module containing the Comment model. That way, any time you can perform a query on Comment, all the relevant models are already loaded.
In my project GAE framework I have solve this issue. On the first page loading I have load all models in memory.
What if we have models with the same name, for example Comment model in "blog" and "board" applications? In this case we have automatically add prefix for models for the King of this model. In result we have the different names for models in different applications: BlogComment and BoardComment.
You can learn more in source code to understand how we do this implementation.

Resources