Author: | Michael JasonSmith |
---|---|
Contact: | Michael JasonSmith <mpj17@onlinegroups.net> |
Date: | 2015-01-14 |
Organization: | GroupServer.org |
Copyright: | This document is licensed under a Creative Commons Attribution-Share Alike 4.0 International License by OnlineGroups.net. |
This is the core code for determining if a group member can post. The mailing list code [1], the Topic interface [2] and Start a Topic [3] rely on this code for determining if a member can post.
In this document I present how the rules for posting are created for each different type of group. I then discuss the viewlets and the notifications that is sent to those that cannot post. See the discussion group [4] for an example of an implementation of some posting rules.
In this section I present the structure of a single rule, and how the Can Post adaptor is used to collect the rules for each group. I then provide an example of chaining rules.
A rule is an adaptor. It takes a user [5] and a group [6]. It provides four properties.
weight
- An integer that has two related functions. First, it is used as the sort-key to determine the order that the rules are checked (see can post adaptor below). Second, it uniquely identifies the rule. (No two rules should have the same weight as this can lead to ambiguity.)
canPost
- A Boolean value that is
True
if this rule thinks that the user can post the the group. status
- A Unicode value that summaries why the user should be prevented from posting to the group.
statusNum
An integer that is one of three values.
-1
if it is unknown whether the user can post to the group (canPost
will beFalse
in this case),0
if the user can post to the group (canPost
isTrue
), and- Set to the
weight
value if the user cannot post to the group (canPost
isFalse
).
In practice most rules inherit from the BaseRule
abstract
base-class. It provides three methods decorated with @Lazy
to
provide the three properties [7]. To make a concrete rule three
things are needed:
- The check method,
- Some constants, and
- The ZCML.
The check
method of a rule performs the actual check to see if a
user can post to a group. Based on the result it sets the values in
the dictionary self.s
. This dictionary is then used to provide the
return values for the three properties of the rule.
For example, the BlockedFromPosting
rule checks to see if the
identifier of the user is in the blocked_members
property of the
mailing list. It then sets the canPost
, status
and
statusNum
values of the self.s
dictionary accordingly. Finally,
it sets self.s['checked']
to True
. This prevents the system
from performing the check more than once.
Each rule must provide a __doc__
and a weight
.
The doc-string (__doc__
) is used to provide documentation on the
rule, which is shown on the page rules.html
in each group.
The weight
is used for two things. First, the can post adaptor
uses it to sort rules, and determine the order that the rules should
be checked [8]. Each weight should be unique, to prevent
ambiguity. Because of this the weights provide a very useful value for
the statusNum
of each rule.
The ZCML sets up each rule as an adaptor [9]. It adapts a
userInfo
and the specific group type and provides an
IGSCanPostRule
. The adaptor must be a named adaptor, as multiple
rules are used for each group. The names are also shown on the
rules.html
page in each group.
The CanPost
adaptor looks very very very much like the adaptor for
a single rule. However, rather than providing a single rule it
aggregates all the rules for a group, giving the final answer as to
weather the user can post. It provides the answer using the same three
properties as the rules: canPost
, status
and statusNum
.
The core of the CanPost
code are two loops. The first gets all the
rules for the current group:
retval = [a for a in gsm.getAdapters((self.userInfo, self.group), IGSCanPostRule)]
This is later sorted by the weight
of each rule (see constants).
The second loop determines if the user can post:
reduce(and_, [rule.canPost for rule in self.rules], True)
Only one CanPost
adaptor is needed for all group-types. That is
because the first loop will retrieve only the rules that are specific
to the current group-type.
The core GroupServer group types use the following inheritance hierarchy for their interfaces:
gs.group.base.interfaces.IGSGroupMarker △ △ │ │ │ gs.group.type.discussion.interfaces.IGSDiscussionGroup │ △ │ │ │ gs.group.type.announcement.interfaces.IGSAnnouncementGroup │ gs.group.type.support.interfaces.IGSSupportGroup
This egg (gs.group.member.canpost
) provides one rule, for the
IGSGroupMarker
— which prevents people who have been explicitly
blocked from posting. All other group types inherit this rule because
their marker-interfaces inherit from the IGSGroupMarker
.
The discussion group (IGSDiscussionGroup
) provides the most rules:
six in all. All these rules are inherited by the announcement group
because its marker-interface (IGSAnnouncementGroup
) inherits from
the discussion group. The announcement group also provides its own rule,
to ensure that only posting members can post.
The support group (IGSSupportGroup
) provides no extra rules, so it
just has the rule that is provided by this package for all the
IGSGroupMarker
groups.
Each rule will need a viewlet that provides feedback about why a person cannot post. The code for each viewlet is relatively simple:
Each viewlet inherits from
gs.group.member.canpost.viewlet.RuleViewlet
,The
weight
for each viewlet is taken from the weight for the respective rule, andThe
show
attribute is set from:self.canPost.statusNum == self.weight``
The viewlets appear in two places. First, they are shown at the bottom of the Topic page if the person viewing the page cannot post. Second, they are shown in the notifications.
There are two notifications: the cannot post notification is sent to people with a profile who cannot post, while unknown email address is sent when the email address is not recognised.
The Cannot Post notification is sent out to people who post to the group, but the rules block the post. The notification contains the viewlets [10]. As such care should be taken to ensure that each viewlet makes sense outside the context of the group, and all links in each viewlet are absolute links that include the site name.
The Cannot Post notification can be previewed by viewing the pages
cannot-post.html
and cannot-post.txt
within each group.
The notification email is sent using a variant of the class
gs.profile.notify.sender.MessageSender
. The main difference is the
notification is constructed differently, so it can include the original
email message that was blocked. The notification email is made up of
five parts:
┌──────────────────────────┐ │multipart/mixed │ │┌────────────────────────┐│ ││ multipart/alternative ││ ││┌──────────────────────┐││ │││┌────────────────────┐│││ ││││text/plain ││││ │││└────────────────────┘│││ │││┌────────────────────┐│││ ││││text/html ││││ │││└────────────────────┘│││ ││└──────────────────────┘││ │└────────────────────────┘│ │┌────────────────────────┐│ ││ message/rfc822 ││ │└────────────────────────┘│ └──────────────────────────┘
- The text of the Cannot Post notification is contained within two
components:
text/plain
contains thecannot-post.txt
message, andtext/html
components contains thecannot-post.html
.
- The two text block are wrapped in a
multipart/alternative
block. - The message that could not be posted is placed in a
message/rfc822
block at the end of the email. - Finally, everything is wrapped in a
multipart/mixed
block, which carries the subject line, addresses, and the rest of the headers.
The unknown email address notification can be thought of as a highly
specialised form of Cannot Post. It is sent when the mailing list
(Products.XWFMailingListManager.XWFMailingList
) fails to recognise the
email address of the sender of a message.
The notification is constructed the same way as the cannot post
notification, with the same five parts. The text encourages the recipient
to add the email address to his or her profile: we speculate that existing
members posting from an unknown email address is the most common reason for
receiving the notification. The rest of the message is similar to the "Not
a Member" message that is sent by the standard Cannot Post
notification. The text can be previewed by looking at the
unknown-email.html
and unknown-email.txt
within each group.
The unknown-email notifier (unknownemail.Notifier
within this egg)
avoids all use of the gs.profile.notify
system — because there is not
profile to sent the notification to! To send the notification the code
assembles the email message, and sends the post using
gs.email.send_email
.
The unknown email address notification should probably appear in the code that handles the mailing list. However, that product [1] is due for a huge refactor, so the unknown email address notification was placed here for safe-keeping. In the future this notification should be moved closer to the mailing list.
- Code repository: https://github.com/groupserver/gs.group.member.canpost
- Questions and comments to http://groupserver.org/groups/development
- Report bugs at https://redmine.iopen.net/projects/groupserver
[1] | (1, 2) See <https://github.com/groupserver/Products.XWFMailingListManager> |
[2] | See <https://github.com/groupserver/gs.group.messages.topic> |
[3] | See <https://github.com/groupserver/gs.group.messages.starttopic/> |
[4] | See <https://github.com/groupserver/gs.group.type.discussion> |
[5] | The user is almost always a
Products.CustomUserFolder.interfaces.IGSUserInfo instance. |
[6] | The group will be a group-folder that has been marked with an interface that is generally specific to the type of group. |
[7] | The
It also initialises the dictionary |
[8] | The use of a weight to sort the rules was taken from
the zope.viewlet code. Indeed, the entire structure of this
system was inspired by that code. |
[9] | It easier to use ZCML to set up the adaptor for each rule because rules can be mixed and matched by different group-types. By using ZCML the mixing-and-matching can be done with very little Python code. |
[10] | The Cannot Post notification contains each viewlet in two forms: the normal HTML version, and a plain-text version, which the notification generates from the HTML. |