E-Democracy Pages Wiki

Search Wiki




Difference between revisions of "Installing GroupServer and E-Democracy"

From E-Democracy.org

(Fix Postgresql Transactions Limit and Authentication)
(Fix Postgresql Transactions Limit and Authentication)
Line 56: Line 56:
postgresql.conf should already contain a commented line that sets max_prepared_transactions, so just find that line, uncomment it and modify it so that max_prepared_transactions is any non-zero value.
postgresql.conf should already contain a commented line that sets max_prepared_transactions, so just find that line, uncomment it and modify it so that max_prepared_transactions is any non-zero value.
Postgresql out of the box also does not allow you to login to a database by supplying a username/password to the command line client (psql). To change this, edit /etc/postgresql/<version>/main/pg_hba.conf, find the following line:
Postgresql out of the box also does not allow you to login to a database by supplying a username/password to the command line client (psql). If you plan to do any development work and want to be able to peek inside the database (you probably will), this will become super annoying. To change this, edit /etc/postgresql/<version>/main/pg_hba.conf, find the following line:
  local  all            all                                    peer
  local  all            all                                    peer

Revision as of 14:10, 16 April 2013

This page attempts to walk you through the installation of GroupServer, E-Democracy's custom eggs, and the ZMI files required to make many of E-Democracy's pages work as they do on http://forums.e-democracy.org. The resulting local instance will not be a perfect replica of what is currently hosted at http://forums.e-democracy.org, but it will provide an instance that you can use to develop on E-Democracy's code.

If you are interested in simply installing and developing on core GroupServer, please visit http://groupserver.org/downloads/ to download the most recent release and find instructions on how to install it.

Set Hosts

The absolute first thing you need to do is decide on the host name you will use to access your local instance of GroupServer/E-Democracy. Zope will behave differently based on what URL you use to access it, and it is best to choose a host name besides 'localhost'.

The host name used as an example through this document is 'e-dem-box'.

Once you have decided on a host name, you must edit /etc/hosts to add a reference for your host name. If your hosts file contains lines for both and (common in Debian and Ubuntu), add a reference for your chosen name on the line.


In the following list, the brackets [] contain the name of the apt package to get.

  • Debian based OS (Debian and Ubuntu both known to work)
  • Sudo [sudo]
  • Git [git]
  • Mercurial* [mercurial]
  • Python 2.7 [python2.7]
  • Postfix [postfix]
  • PostgreSQL [postgresql]
  • Nginx [nginx]
  • libxslt1-dev [libxslt1-dev]

It actually is possible to install E-Democracy without Mercurial. However, GroupServer's code is kept in Mercurial repos, so if you plan on doing any programming, you will need to have Mercurial installed to push your changes.

There is a great write-up by a user who attempted to install GroupServer on Scientific Linux (a variant of RHEL), which gives hope to those who want to install on rpm based distros. That said, it will probably be a lot easier to just install Ubuntu or Debian.


If you are installing on Ubuntu, you can skip this section, since sudo is already installed and setup on Ubuntu.

Several of the steps that follow require root access. More importantly, gs_install_ubuntu.sh (the file that will drive a lot of the installation) assumes that sudo is available and configured for the user that runs it. In theory it is possible to run gs_install_ubuntu.sh as root, but this will mess up the permissions of all files created in the process, and will make it harder in the future to develop on E-Democracy as a regular user.

Assuming that sudo is installed, you can run the command visudo as root to edit the sudoers file, which controls how sudo works. In this file, find the following line:

root    ALL=(ALL:ALL) ALL

and add a similar line for your regular system user (called 'super-programmer-hero' here):

super-programmer-hero    ALL=(ALL:ALL) ALL

Save the file and exit the editor.

Configure Postfix

While installing the required packages, you will be asked to configure Postfix. Fortunately, Ubuntu provides an easy to follow example of how to configure Postfix that you can follow. The goal here isn't to make Postfix work (the linked to configuration doesn't fully succeed in doing that), but to provide GroupServer with enough of a Postfix installation/configuration to make it think it can send/receive emails (while developing, there will be other ways by which we can feed GroupServer emails and observe what it wants to send out.)

Fix Postgresql Transactions Limit and Authentication

Postgresql out of the box is not configured to support transactions, which GroupServer makes use of. Thus, you will have to edit /etc/postgresql/<version>/main/postgresql.conf so that it contains the following line:

max_prepared_transactions = 10 

postgresql.conf should already contain a commented line that sets max_prepared_transactions, so just find that line, uncomment it and modify it so that max_prepared_transactions is any non-zero value.

Postgresql out of the box also does not allow you to login to a database by supplying a username/password to the command line client (psql). If you plan to do any development work and want to be able to peek inside the database (you probably will), this will become super annoying. To change this, edit /etc/postgresql/<version>/main/pg_hba.conf, find the following line:

local   all             all                                     peer

change it to

local   all             all                                     md5

And save.

Now restart postgresql:

service postgresql restart

Configure Nginx

You will want to add a site configuration for E-Democarcy. This is important for two reasons:

  1. Its always good to create a configuration for any web-app you are developing on, since you may have multiple web apps you are working on at any given time
  2. The Nginx configuration does two very important things:
    1. It inserts a string into the URL of requests that tells GroupServer to apply E-Democracy's custom eggs/skin
    2. It inserts a header that tells GroupSever which configuration instance to use

On your file system go to /etc/nginx/sites-available and create a file named 'e-democracy'. In e-democracy, insert the following:

upstream uwsgi_upstream {
    server e-dem-box:8080;

server {
    listen 80;
    server_name e-dem-box;
    location / {
        rewrite /(.*) /++skin++skin_gs_ogn_edem/VirtualHostBase/http/$host/groupserver/Content/initial_site/VirtualHostRoot/$1 break;
        proxy_pass http://uwsgi_upstream/;
        proxy_set_header INSTANCEID edemtest;
        include proxy_params;

Note that in the above we refer to the host name established earlier. Replace 'e-dem-box' with whatever host name you choose. Also note the value of INSTANCEID we insert; this must correspond to a configuration instance that we will create in gsconfig.ini later.

Save /etc/nginx/sites-available/e-democracy. Then go into /etc/nginx/sites-enabled and create a link to sites-available/e-democarcy:

ln -s ../sites-available/e-democracy e-democracy

Now restart Nginx:

service nginx restart

Get the E-Democracy Config Files

Visit E-Democracy's buildout-config repo and grab the URI that you wish to use. Then, in your development space on your file system, create a folder named 'e-democracy' and cd into it. Finally, clone the above repo. It's recommended that you clone into a folder with a name that makes sense, such as 'forums', 'forums.e-democracy.org', or 'groupserver'. Thus, your clone command might look like:

git clone https://github.com/e-democracy/buildout-config.git forums

This will fetch all of the files that are needed to build GroupServer and E-Democracy's custom eggs. This repo is also used to save and share changes to the buildout configuration. If you plan to develop on E-Democracy, always be sure to do a git pull to get the latest buildout configuration and eggs.

Set config.cfg

Cd into the folder you just cloned into. Before we can run the installation script, we must first create and edit config.cfg. A config.cfg.sample is included in the buildout-config repo to make this a lot easier. Copy that:

cp config.cfg.sample config.cfg

Now edit config.cfg. Read the comments, and edit the values as necessary. host and zope_host should be set to the host name you selected earlier ('e-dem-box' in our example). Once you reach the Advanced Configuration Options section you should be done.

Run gs_install_ubuntu.sh

You should now be ready to run the installation script. Go to the folder that you cloned buildout-config into earlier and run the following command, as a regular user without sudo:


Right away you will be prompted for your sudo password. After that, grab some coffee/beer/food, cause this will take a bit of time (20 to 60 minutes depending on your system and Internet connection).

This script will install a few other required system packages. It will then create an environment in which to install GroupServer, and create the databases that GroupServer and Zope need. Finally, it will run buildout, which will fetch the 100+ eggs that GroupServer/E-Democracy require.

Because this relies on fetching so much from the Internet, it is possible the script will fail due to network errors/congestion. If this happens, simply re-run the script. If it continues the fail at the same step, visit http://www.groupserver.org/groups/development and post a description of the problem you are having with the script.

Create dummy error pages

There is a difference between the error handling systems of the production server and our development environment that causes the development environment to redirect to a non-existant page whenever an error/missing page is encountered. This makes reading errors in the log file a lot harder, because it becomes an infinate loop of not found pages.

So, we create a dummy new_unexpected_error.html in the ZMI:

  1. Go to ZMI
  2. Go to groupserver/Content/initial_site
  3. Create page template new_unexpected_error.html
  4. Set Content-Type to text/html
  5. Leave Body as default, or customize if you wish

We also create a dummy new_not_found.html page in the ZMI. It won't display anything useful, but it will make reading the logs a lot easier:

  1. Go to ZMI
  2. Go to groupserver/Content/initial_site
  3. Create page template new_not_found.html
  4. Set Content-Type to text/html
  5. Leave Body as default, or customize if you wish

Make E-Democracy Homepage

  1. Go to ZMI
  2. Go to groupserver/Content/initial_site
  3. Create page template content_en
  4. Set the Content-Type to text/xml
  5. Place the following in it
  tal:define="site here/Scripts/get/division_object">
  <tal:block define="userRoles python:user.getRolesInContext(site)">
    <p tal:condition="python:'DivisionAdmin' in userRoles">
          <bold class="label">As an admin you may:</bold>:
            <a href="/startgroup.html"><strong>Start</strong> a Group</a>
            where people can join and post.
            <a href="/charter"><strong>Change</strong> the default charter</a> 
            which will appear in a group.
            <strong>View</strong> <a href="/stats.html">posting

            <strong><a href="/admin_join_add_csv.html">Upload new members in bulk</a>.</strong>

            <strong>Search</strong> for an existing <a href="/admin_search_people.html">forum member by e-mail</a>. Or <a href="https://zmi.onlinegroups.net/sites/edem/management_scripts/find/user_from_email/ZScriptHTML_tryForm">ZMI by e-mail</a> or <a href="https://zmi.onlinegroups.net/sites/edem/management_scripts/find/user_from_fn/ZScriptHTML_tryForm">profile name</a> for incomplete accounts, "invited" members not on a forum, etc. Requires separate ZMI login.

    <div class="cols">
      <div class="rightColumn">

        <div id="latestPhotos">

          <h2>Latest Photos</h2>
          <div id="pics">
            <p>Loading the latest photos…</p>

          <p><a title="Next 20 photos and files on this site" href="s/index.html?s=&g=&a=&m=&t=0&p=0&f=1&r=0&i=0&l=20">More photos/files from 50+ forums…</a></p>

        <div id="latestTopics">

         <h2>Latest Topics</h2>
          <div class="gs-search search" id="gs-search-topic-site-home-search">
            <div class="gs-search-entry">
              <input type="text" class="textEntry"/>
            <p id="gs-search-topic-site-home-loading" class="gs-search-loading" style="display:none;">
              <img src="/++resource++anim/wait.gif"/> 
              Loading the topics
            <div id="gs-search-topic-site-home-latest" class="gs-search-results">
            <div id="gs-search-topic-site-home-toolbar" class="toolbar gs-search-toolbar">
              <span class="ui-widget-header ui-corner-all">
                <button id="gs-search-topic-site-home-toolbar-prev" class="gs-search-toolbar-previous">Newer</button>
                <button id="gs-search-topic-site-home-toolbar-next" class="gs-search-toolbar-next">Older</button>
          <div id="gs-search-topic-site-home-tasks">
            <p>Subscribe to a <b><a type="application/atom+xml" tal:define="title string:Posts in ${options/siteName};" href="/s/search.atom?t=0&f=0&p=1">web feed</a>,   <a title="Next 20 topics across site" href="/s/index.html?s=&g=&a=&t=1&p=0&f=0&r=0&i=10&l=20">list more topics</a></b> or <b><a title="Latest 100 topics on this site" href="/s/index.html?s=&g=&a=&m=&t=0&p=1&f=0&r=0&i=0&l=100">list individual posts</a></b> across all forums</p>
      <div class="leftColumn">

        <div id="greeting" tal:condition="python:user.has_role('Authenticated')">
          <div id="youLinks" tal:define="nn user/get_canonicalNickname">
            <div class="userimage photo">
              <a href="#"
                 tal:attributes="href string:/p/${nn}/image.html">
               <img src="#" alt="Change Image" tal:attributes="src user/get_image" tal:condition="user/get_image_path"/>
               <span tal:condition="python: user.get_image_path() == None">Upload a Profile Image</span>

          <h2 id="hi" tal:condition="python:'welcome' not in request.form.keys()">
            <span class="salutation">Welcome</span>,
            <span class="fn" tal:content="user/fn">eCampus Member</span>
          <ul class="groups"
              tal:define="groupsInfo python:modules['Products.GSContent'].GSGroupsInfoFactory()(context);
      GSGroupInfoFactory python:modules['Products.GSGroup.groupInfo'].GSGroupInfoFactory();
      memberGroups python:groupsInfo.get_member_groups_for_user(user, user);
      grps python:[GSGroupInfoFactory(g) for g in memberGroups]">

            <li><bold class="label">Your Forums:</bold></li><p></p>
            <tal:block repeat="group grps">
              <li><a class="group" href="#"
                  tal:attributes="href group/url"

          <p>Your invitation to join is powerful. <a href="http://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fforums.e-democracy.org%2F">Share this site</a> with your friends on Facebook.</p>

          <ul id="youLinks" tal:define="nn user/get_canonicalNickname">
              <a href="#" tal:attributes="href string:/p/${nn}/email.html">Change email settings</a> 
              Get daily digests, go web-based, or change/add e-mail address.
              <a href="#" tal:attributes="href string:/p/${nn}/edit.html">Update your profile</a> 
              <a href="#" tal:attributes="href string:/p/${nn}/">View your public profile</a>, 
              <a href="#" tal:attributes="href string:/p/${nn}/edit.html">change it</a>,
              <a href="#" tal:attributes="href string:/p/${nn}/image.html">upload a photo</a>, or 
              <a href="#" tal:attributes="href string:/p/${nn}/password.html">change your password</a> 


            <li>What's Up at E-Democracy.org - For project updates, see our <a title="Project Blog" href="http://blog.e-democracy.org">blog</a>, follow us on <a title="Follow E-Democracy.org on Twitter" href="http://twitter.com/edemo">Twitter</a> or <a title="Fan E-Democracy.org on Facebook" href="http://e-democracy.org/facebook">Facebook</a>.</li>
            <li><a title="Projects Volunteer Group" href="http://forums.e-democracy.org/groups/projects">Volunteer</a> - Help with outreach, design, advertising, software development, fund raising, and new forums.</li>
          <b>Topics Recommended on Facebook</b>
          <iframe src="//www.facebook.com/plugins/recommendations.php?site=forums.e-democracy.org&action&width=400&height=300&header=false&colorscheme=light&linktarget=_blank&border_color&font=tahoma" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:400px; height:300px;" allowTransparency="true"> </iframe>

        <span tal:condition="python:not(user.has_role('Authenticated'))" tal:replace="structure here/introduction"></span>

  1. Go back to initial_site
  2. Go to Properties tab
  3. Replace content in introduction property with the following:
<div class="introduction">

<h3>E-Democracy.org - Inspiring Inclusive Community Engagement Online</h3>

<a href="http://forums.e-democracy.org/getinvolved"><img alt="E-Dem Logo and People" src="http://forums.e-democracy.org/groups/files/files/f/3453-2012-03-29T134456Z/logopeople2_opt.jpg" /></a>

<h3>A Video Tour of our BeNeighbors.org Outreach</h3>

<p>Check out our <a href="http://blog.e-democracy.org/posts/1816">handouts</a> from our virtual booth or watch this video:</p>

<iframe width="400" height="300" src="http://www.youtube.com/embed/Csi7ldKirSI" frameborder="0" allowfullscreen></iframe>

<h3>Join Local Community Forums, Global Online Communities</h3>

<li><a href="http://forums.e-democracy.org/groups">Local Forums</a> Our 50+ local community forums online connect over 20,000 members:</li> 
<li><a href="http://beneighbors.org/edemhome">Twin Cities - BeNeighbors.org</a> - Special outreach</li>
<li><a href="http://forums.e-democracy.org/groups">United States</a></li>
<li><a href="http://ukneighbours.org">United Kingdom</a></li>
<li><a href="http://neighbours.cc">New Zealand</a></li>
<li><a href="http://forums.e-democracy.org/groups/projects">E-Democracy.org Projects</a> - Volunteer here</li>
<li><a href="http://forums.e-democracy.org/practice">Global Communities of Practice</a> - Get involved with digital civic engagement and inclusion</li>
<li><a href="http://forums.e-democracy.org/login.html">Login</a> - Existing Members</li>

<P>New to E-Democracy? Get <a href="http://forums.e-democracy.org/groups/news">our e-news</a>: 

<form enctype="multipart/form-data"  method="post" id="register.form"
  <input id="form.email" name="form.email" size="20" type="text" value="enter e-mail" />
  <input id="form.groupId" name="form.groupId" type="hidden" value="news" />
  <input id="form.came_from" name="form.came_from" type="hidden" value="http://forums.e-democracy.org/groups/news" />
  <input type="submit" id="form.actions.register" name="form.actions.register" value="Register" />
</form><!--GroupSever Signup Form-->

... then <a href="http://forums.e-democracy.org/groups">join some forums</a> or <a href="http://forums.e-democracy.org/about/start">get started</a> with some in-depth advice.</P>

<h3>Video - Let's BeNeighbors.org Twin Cities</h3>

<iframe width="400" height="300" src="http://www.youtube.com/embed/1K84J5NE8Fs" frameborder="0" allowfullscreen></iframe>

<h3>More Pages</h3>

<li><a href="http://e-democracy.org/start">Get Started</a> - How to details</li>
<li><a href="http://blog.e-democracy.org">Blog</a> - News and lessons</li>
<li><a href="http://e-democracy.org/inclusion">Inclusive Community Engagement Online</a> - Major grant-funded effort</li>
<li><a href="http://forums.e-democracy.org/mobile">Mobile</a> - Quick links</li>
<li><a href="http://e-democracy.org/support">Support</a> - Get help</li>
<li><a href="http://e-democracy.org/about">About E-Democracy</a> - <a href="http://e-democracy.org/contact">Contact us</a></li>

<h3>Help Us Reach 25,000 Participants!</h3>

<p><a href="http://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fforums.e-democracy.org%2F">Your invitation</a> to join is powerful. <a href="http://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fforums.e-democracy.org%2F">Share us via Facebook.</a> </p>

Share Site: <!-- AddThis Button BEGIN -->
<div class="addthis_toolbox addthis_default_style ">
<a class="addthis_button_facebook_like" fb:like:layout="button_count"></a>
<a class="addthis_button_tweet"></a>
<a class="addthis_counter addthis_pill_style"></a>
<script type="text/javascript">var addthis_config = {"data_track_clickback":true};</script>
<script type="text/javascript" src="http://s7.addthis.com/js/250/addthis_widget.js#username=edemocracy"></script>
<!-- AddThis Button END -->


<script charset="utf-8" src="http://widgets.twimg.com/j/2/widget.js"></script>
new TWTR.Widget({
  version: 2,
  type: 'profile',
  rpp: 4,
  interval: 30000,
  width: 400,
  height: 300,
  theme: {
    shell: {
      background: '#3DA7D1',
      color: '#616161'
    tweets: {
      background: '#fcfcfc',
      color: '#361a36',
      links: '#black'
  features: {
    scrollbar: false,
    loop: false,
    live: false,
    behavior: 'all'


<P><b>Topics Recommended on Facebook</b>
<iframe src="//www.facebook.com/plugins/recommendations.php?site=forums.e-democracy.org&action&width=400&height=300&header=false&colorscheme=light&linktarget=_blank&border_color&font=tahoma" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:400px; height:300px;" allowTransparency="true"></iframe>

<p>Follow us on <a href="http://www.facebook.com/edemocracyorg">Facebook</a>:</P>
 <iframe src="http://www.facebook.com/plugins/like.php?href=http%3A%2F%2Fwww.facebook.com%2Fedemocracyorg&layout=standard&show_faces=true&width=300&action=like&font=tahoma&colorscheme=light&height=40"
scrolling="no" frameborder="0" style="border:none; overflow:hidden;
width:300px; height:80px;" allowTransparency="true"></iframe>


Homepage JS and Metadata

  1. In /groupserver/Content/initial_site, create page tempate named javascript.xml
  2. Set Content-Type to text/xml
  3. Set body to:
<tal:block xmlns:tal="http://xml.zope.org/namespaces/tal">
  <script src="/++resource++jquery-ui-1.8.9.custom.min.js" type="text/javascript"> </script>
  <script type="text/javascript">

    var GSHideRightCol = function () {
      jQuery('.rightColumn').fadeOut('slow', GSMakeLeftColNotACol);

    var GSMakeLeftColNotACol = function () {
      jQuery('.leftColumn').attr('class', 'not-leftColumn')

    var GSLoadTopicsOrNot = function(responseText, textStatus, XMLHttpRequest){
        if ( /.*topic-search-none.*/.test(responseText) ) {
            setTimeout("GSHideRightCol()", 1);

    var uri = "/s/search.ajax?mg=1&i=0&l=10";
     jQuery('#latestTopicsFill').load(uri, GSLoadTopicsOrNot)

  <script type="text/javascript" src="jquery.bxGallery.min.js"> </script>
  <script type="text/javascript">
    var add_gallery = function (responseText, textStatus, XMLHttpReques) {
      var opts = {thumbwidth: 72, thumbheight: 72, 
    jQuery(document).ready( function () {
      jQuery('#pics').load('/s/images.html?mg=1&t=0&f=1&l=5&m=image/jpeg&m=image/png', add_gallery);
  <script src="/++resource++gs-search-base-js-20121217.js" type="text/javascript" > </script>
  <script src="/++resource++gs-search-topic-site-homepage-20120606.js" type="text/javascript" > </script>

  1. Create page template 'metadata.xml'
  2. Set Content-Type to text/html
  3. Set Body to:
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="robots" content="noodp">

Make Digests Work

  1. Go to ZMI
  2. Go to /groupserver/Content/initial_site
  3. Create a page template named digest_news.txt
  4. Set Content-Type to text/html
  5. Set body to the following (plain text):
Hello World

  1. Create a page template named digest_news.xml
  2. Set Content-Type to text/xml
  3. Set body to the following:
<p>Hello World!</p>

Make /groups Work

  1. Go to ZMI
  2. Go to /groupserver/Content/groups
  3. (If needed) Create a pate template named content_en
  4. (If needed) Set Content-Type to text/xml
  5. Set body to the following:
<html xmlns:tal="http://xml.zope.org/namespaces/tal"
    <title>Groups Homepage</title>
    <meta property="dc:created">2006-11-23</meta>
    <meta property="dc:publisher">OnlineGroups.Net</meta>
    <meta property="dc:rightsHolder">OnlineGroups.Net</meta>
    <meta property="dc:creator">Michael JasonSmith</meta>
    <meta property="dc:description">
      A list of all groups on eDem.org.
    <meta property="dc:licence"/>
    <!--Uncomment the following and add your name, if you make-->
    <!--  any modifications-->
    <!--<meta property="dc:contributor"></meta>-->
    <section tal:define="site here/Scripts/get/division_object;
      siteName python:modules['Products.XWFCore.XWFUtils'].getOption(here, 'siteName') or site.title_or_id();
      userAuthenticated python:user.has_role('Authenticated');
      userAnonymous python:user.has_role('Anonymous');
      isSiteAdmin python:'DivisionAdmin' in user.getRolesInContext(site);
      collatedGroups python:here.collate_groups(site, user);
      groupsObj python:collatedGroups[0];
      groups python:collatedGroups[1];
      userGroups python:collatedGroups[2];
      nonUserGroups python:collatedGroups[3]; 
      joinableGroups python:collatedGroups[4];
      otherGroups python:collatedGroups[5]">

      <h><span class="site" tal:content="siteName"/></h>
      <p tal:condition="userAuthenticated">
        This page shows the online groups that are currently visible to you.
        <a class="helpLink" href="/help/manual/#groupserver-managing-groups" 
          title="Managing Groups Help">[?]</a>
      <p tal:condition="not:userAuthenticated">
        This page shows all the online groups (Issues Forums, etc.) that are visible to anyone. Generally, they are listed by country first and state second.  For each state, the city and neighborhood forums are listed. 
        <span tal:condition="python:len(groups)==0">This site may contain
          online groups that are visible only to logged in members;</span>
        <a tal:attributes="title string:Please login to ${siteName}"
          href="/login.html?came_from=/groups">Login</a> to see the forums
        of which you are member or <a tal:attributes="title string:Register for ${siteName}"
          href="/request_registration.html?came_from=/groups">register</a> and to join one or more forums. 
      <p class="adminTasks" tal:condition="isSiteAdmin" >
        As a site administrator you can
        <a title="Start a group"  href="/admindivision/start_a_group/">Start
          a group.</a>
      <section id="your-groups" tal:condition="userGroups">
        <h>Your Forums</h>
        <p>You belong to the following forums.</p>
          <li tal:repeat="group userGroups">
            <a class="group" tal:content="group/title_or_id"
              tal:attributes="title string:${group/title_or_id} Homepage;
              href string:/groups/${group/getId}"/>:
              tal:define="member_count python:here.Scripts.get.group_member_count(group)">
              <span tal:replace="member_count"/>
              <span tal:replace="python:member_count == 1 and 'member' or 'members'"/>.
            <a tal:attributes="title string:Leave ${group/title_or_id};
              href string:/groups/leave.html?groupId=${group/getId}">(leave)</a>

      <section tal:define="categories python:here.group_categories(groups);
        categoryNames python:here.sort_category_keys(categories.keys())"
        tal:condition="python:userAnonymous or (joinableGroups or otherGroups)">
        <h tal:condition="not:userAnonymous">Other Forums</h>
        <p tal:condition="not:userAnonymous">
          You are not a member of the following forums, but you may
          view them, and possibly join some.
        <section tal:repeat="categoryName categoryNames">
            tal:define="category python:categories[categoryName];
            fora python:filter(lambda f: f not in userGroups, category);
            sortedFora python:here.sort_groups_by_title(fora);" 
            <h tal:content="categoryName">Category Name</h>
              <tal:block tal:repeat="forum sortedFora">

                <!-- Next tal added by Tim Erickson to hide groups without categoryName  -->
                <tal:block condition="python:categoryName">

                <li tal:condition="python:forum not in userGroups"
                  tal:define="member_count python:here.Scripts.get.group_member_count(forum);
                  realLifeGroup python:forum.getProperty('real_life_group','');
                  description python:forum.getProperty('description', '')">
                  <span class="group" 
                    tal:attributes="href forum/getId"

                  <tal:block tal:condition="description"> 
                    <!-- for eDem -->
                    <span class="realLifeGroup" tal:content="description"/>;
                  <span tal:replace="member_count"/>
                  <span tal:replace="python:member_count == 1 and 'member' or 'members'"/>.
                  <a href="#" title="Join this forum"
                    tal:condition="python:userAuthenticated and (forum in joinableGroups)"
                    tal:attributes="href string:/groups/${forum/getId}/join.html;
                    title string:Join ${forum/title_or_id}">(Join this forum.)</a>
                  <a href="#" title="Register, and join this forum"
                    tal:condition="python:(not userAuthenticated) and (forum in joinableGroups)"
                    tal:attributes="href string:request_registration.html?form.groupId=${forum/getId}">(register and join)</a>
                  <tal:block tal:define="joinability python:here.Scripts.get.group_joinability(site.getId(), forum.getId())"
                    tal:condition="python:joinability == 'invite'">
                    <!--(Only invited members can join this forum.)-->


  1. In /groupserver/Content/initial_site/groups, create Script (Python) named collate_groups
  2. Set Parameter List to: site, user
  3. Set Body to:
group_membership = user.getGroups() or []
group_membership_ids = map(lambda x: x.split('_member')[0], group_membership)

userAuthenticated = user.has_role('Authenticated')
userAnonymous = user.has_role('Anonymous')
groupsObj = context.Scripts.get.groups_object()
groups = filter(lambda x: getattr(x, 'is_group', 0),
                context.Scripts.get.object_values(groupsObj, 'Folder'))

userGroups = (not userAnonymous) and filter(lambda x: x.getId() in group_membership_ids, groups) or []
nonUserGroups = filter(lambda x: x.getId() not in userGroups, groups)
joinableGroups = []
otherGroups = []
for group_object in nonUserGroups:
    if group_object.getProperty('join_condition', 'anyone') == 'anyone':
return groupsObj, groups, userGroups, nonUserGroups, joinableGroups, otherGroups

  1. In /groupserver/Content/initial_site/groups, create Script (Python) named group_categories
  2. Set Parameter List to: groups=None
  3. Set Body to:
# Get all group-categories in a site

if groups == None:
    groupsObj = context.Scripts.get.groups_object()
    groups = filter(lambda x: getattr(x, 'is_group', 0),
                context.Scripts.get.object_values(groupsObj, 'Folder'))
categories = {}

for group in groups:
    category = group.getProperty('category',  'Uncategorised')
    categories[category] = categories.get(category, []) + [group]

#assert categories
return categories

  1. In /groupserver/Content/initial_site/groups, create Script (Python) named sort_category_keys
  2. Set Parameter List to: keys
  3. Set Body to:
def alpha_sort(a, b):
    alpha = a.lower()
    beta = b.lower()
    if alpha < beta:
        retval = -1
    elif alpha == beta:
        retval = 0
        retval = 1
    return retval
retval = keys
return retval

  1. In /groupserver/Content/initial_site/groups, create Script (Python) named sort_groups_by_title
  2. Set Parameter List to: groups
  3. Set Body to:
def alpha_sort(a, b):
    alpha = a.title_or_id().lower()
    beta = b.title_or_id().lower()
    if alpha < beta:
        retval = -1
    elif alpha == beta:
        retval = 0
        retval = 1
    return retval
retval = groups
return retval

  1. In /groupserver/Content/initial_site/groups, create page template named groups_intro.xml
  2. Set Content-Type to text/html
  3. Set Body to:
<p>Select by place for community <a href="http://e-democracy.org/if">Issues Forums</a> or join the Online Communities and Project online groups that we host below.</p> 
<p>No <a href="http://e-democracy.org/if">Issues Forum</a> in your community? <a href="http://e-democracy.org/contact">Contact Us</a> to propose one.</p>

Set up XSLT

  1. Go to Properties tab of /groupserver/Content/initial_site/groups/content_en
  2. Set transform_paths to: Presentation/Tofu/XHTML2/xslt/
  3. Save
  4. Set property named 'transform_html' to 'xhtml2-transform_body-content-only (xslt)'. Leave the other transform properties as 'do not use transform'
  5. Save

Set category properties

  1. In /groupserver/Content/initial_site/groups, go to Properties tab
  2. Create a property named 'categories' of type lines
  3. Once saved, enter a couple of categoires ('Test', 'Empty')
  4. For each group that current exists
    1. Go into the group's folder
    2. View the group's Properties
    3. Create a property named 'category' of type 'string', with a vlue equal to one of the categories entered above

Home - Mobile - Forums - Wiki - Blog - About - Help - Contact - People - Donate - Rules - Archives