- Author
- Contact
Michael JasonSmith <mpj17@onlinegroups.net>
- Date
2015-01-14
- Organization
- 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 code1, the Topic interface2 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 group4 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 user5 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 properties7. 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 checked8. 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. 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, and The
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 9. 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 product10 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
See <https://github.com/groupserver/Products.XWFMailingListManager>↩
See <https://github.com/groupserver/gs.group.messages.topic>↩
See <https://github.com/groupserver/gs.group.messages.starttopic/>↩
See <https://github.com/groupserver/gs.group.type.discussion>↩
The user is almost always a
Products.CustomUserFolder.interfaces.IGSUserInfo
instance.↩The group will be a group-folder that has been marked with an interface that is generally specific to the type of group.↩
The
BaseRule
also supplies four other useful properties:- A
userInfo
, - A
groupInfo
, - A
siteInfo
and - A
mailingListInfo
.
It also initialises the dictionary
self.s
that thecanPost
,status
andstatusNum
properties use.↩- A
The use of a
weight
to sort the rules was taken from thezope.viewlet
code. Indeed, the entire structure of this system was inspired by that code.↩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.↩
See <https://github.com/groupserver/Products.XWFMailingListManager>↩