def setUp(self): super(ListMembersOptionsTest, self).setUp() self.domain = get_mailman_client().create_domain('example.com') self.foo_list = self.domain.create_list('foo') self.user = User.objects.create_user('testuser', '*****@*****.**', 'testpass') self.superuser = User.objects.create_superuser('testsu', '*****@*****.**', 'testpass') self.owner = User.objects.create_user('testowner', '*****@*****.**', 'testpass') self.moderator = User.objects.create_user('testmoderator', '*****@*****.**', 'testpass') for user in (self.user, self.superuser, self.owner, self.moderator): EmailAddress.objects.create(user=user, email=user.email, verified=True) self.foo_list.add_owner('*****@*****.**') self.foo_list.add_moderator('*****@*****.**') self.mm_user = get_mailman_client().create_user('*****@*****.**', '') self.mm_user.addresses[0].verify() self.foo_list.subscribe('*****@*****.**', pre_verified=True, pre_confirmed=True, pre_approved=True) self.url = reverse('list_member_options', args=( self.foo_list.list_id, '*****@*****.**', ))
def all(self): try: return getattr(get_mailman_client(), self.resource_name_plural) except AttributeError: raise MailmanApiError except MailmanConnectionError as e: raise MailmanApiError(e)
def update_from_mailman(self): try: client = get_mailman_client() mm_list = client.get_list(self.name) except MailmanConnectionError: return except HTTPError: return # can't update at this time if not mm_list: return def convert_date(value): value = dateutil.parser.parse(value) if value.tzinfo is None: value = value.replace(tzinfo=utc) return value converters = { "created_at": convert_date, "archive_policy": lambda p: ArchivePolicy[p].value, } for propname in self.MAILMAN_ATTRIBUTES: try: value = getattr(mm_list, propname) except AttributeError: value = mm_list.settings[propname] if propname in converters: value = converters[propname](value) setattr(self, propname, value) self.save()
def setUp(self): self.mm_client = get_mailman_client() if self.use_vcr: cm = self._mm_vcr.use_cassette('.'.join([ self.__class__.__name__, self._testMethodName, 'yaml'])) self.cassette = cm.__enter__() self.addCleanup(cm.__exit__, None, None, None)
def bans_view(request, template, list_id=None): """Ban or unban email addresses. This is a reusable view which works for both global and list specific bans. Whether a MailingList ban is updated or a Global one depends on list_id being passed in. :list_id: MailingList Id if this is a List ban, None otherwise. """ if list_id: m_list = List.objects.get_or_404(fqdn_listname=list_id) url = reverse('list_bans', args=[list_id]) ban_list = m_list.bans else: ban_list = get_mailman_client().bans url = reverse('global_bans') m_list = None # Process form submission. if request.method == 'POST': if 'add' in request.POST: addban_form = AddBanForm(request.POST) if addban_form.is_valid(): try: ban_list.add(addban_form.cleaned_data['email']) messages.success(request, _( 'The email {} has been banned.'.format( addban_form.cleaned_data['email']))) except HTTPError as e: messages.error( request, _('An error occurred: %s') % e.reason) except ValueError as e: messages.error(request, _('Invalid data: %s') % e) return redirect(url) elif 'del' in request.POST: try: ban_list.remove(request.POST['email']) messages.success(request, _( 'The email {} has been un-banned'.format( request.POST['email']))) except HTTPError as e: messages.error(request, _('An error occurred: %s') % e.reason) except ValueError as e: messages.error(request, _('Invalid data: %s') % e) return redirect(url) else: addban_form = AddBanForm(initial=request.GET) banned_addresses = paginate( list(ban_list), request.GET.get('page'), request.GET.get('count')) context = { 'addban_form': addban_form, 'banned_addresses': banned_addresses, } if list_id: context['list'] = m_list return render(request, template, context)
def public_profile(request, user_id): class FakeMailmanUser(object): display_name = None created_on = None addresses = [] subscription_list_ids = [] user_id = None try: client = get_mailman_client() mm_user = client.get_user(user_id) except HTTPError: raise Http404("No user with this ID: %s" % user_id) except mailmanclient.MailmanConnectionError: mm_user = FakeMailmanUser() mm_user.user_id = user_id # XXX: don't list subscriptions, there's a privacy issue here. # # Subscriptions # subscriptions = get_subscriptions(mm_user, db_user) all_votes = Vote.objects.filter(email__sender__mailman_id=user_id) likes = all_votes.filter(value=1).count() dislikes = all_votes.filter(value=-1).count() likestatus = "neutral" if likes - dislikes >= 10: likestatus = "likealot" elif likes - dislikes > 0: likestatus = "like" # This is only used for the Gravatar. No email display on the public # profile, we have enough spam as it is, thank you very much. try: addresses = [str(addr) for addr in mm_user.addresses] except (KeyError, IndexError): addresses = [] fullname = mm_user.display_name if not fullname: fullname = Email.objects.filter(sender__mailman_id=user_id).exclude( sender_name="", sender_name__isnull=True).values_list("sender_name", flat=True).first() if mm_user.created_on is not None: creation = dateutil.parser.parse(mm_user.created_on) else: creation = None posts_count = Email.objects.filter(sender__mailman_id=user_id).count() is_user = request.user.is_authenticated and bool( set([str(a) for a in mm_user.addresses]) & set(request.user.hyperkitty_profile.addresses)) context = { "fullname": fullname, "creation": creation, "posts_count": posts_count, "likes": likes, "dislikes": dislikes, "likestatus": likestatus, "addresses": addresses, "is_user": is_user, } return render(request, "hyperkitty/user_public_profile.html", context)
def all(self, advertised=False): try: method = getattr(get_mailman_client(), 'get_' + self.resource_name_plural) return method(advertised=advertised) except AttributeError: raise MailmanApiError except MailmanConnectionError as e: raise MailmanApiError(e)
def get(self, *args, **kwargs): try: method = getattr(get_mailman_client(), 'get_' + self.resource_name) return method(*args, **kwargs) except AttributeError as e: raise MailmanApiError(e) except HTTPError as e: if e.code == 404: raise Mailman404Error('Mailman resource could not be found.') else: raise except MailmanConnectionError as e: raise MailmanApiError(e)
def system_information(request): client = get_mailman_client() all_configs = client.system configs = [] for key, name in SYSTEM_INFO_KEYS: configs.append((name, all_configs.get(key))) return render( request, 'postorius/system_information.html', {'configs': configs}, )
def import_list_from_mailman(list_id): from hyperkitty.models import MailingList mmclient = get_mailman_client() try: mm_list = mmclient.get_list(list_id) except (MailmanConnectionError, HTTPError): return mlist, created = MailingList.objects.get_or_create( name=mm_list.fqdn_listname) if created: logger.info("Imported the new list %s from Mailman", mm_list.fqdn_listname) mlist.update_from_mailman()
def _get_list_page(count, page): filtering = settings.MAILMAN_LIST_INDEX_FILTERING client = get_mailman_client() if filtering == 'owner-only' and not request.user.is_superuser: return client.get_list_page(owner=request.user.email, count=count, page=page) advertised = not request.user.is_superuser return client.get_list_page(advertised=advertised, count=count, page=page)
def create(self, *args, **kwargs): try: method = getattr(get_mailman_client(), 'create_' + self.resource_name) return method(*args, **kwargs) except AttributeError as e: raise MailmanApiError(e) except HTTPError as e: if e.code == 409: raise MailmanApiError else: raise except MailmanConnectionError: raise MailmanApiError
def all(self, only_public=False): try: objects = getattr(get_mailman_client(), self.resource_name_plural) except AttributeError: raise MailmanApiError except MailmanConnectionError as e: raise MailmanApiError(e) if only_public: public = [] for obj in objects: if obj.settings.get('advertised', False): public.append(obj) return public else: return objects
def handle(self, *args, **options): # choose an interpreter try: import IPython console_fn = IPython.embed except ImportError: import code shell = code.InteractiveConsole(globals()) console_fn = shell.interact # connect to mailmanclient client = get_mailman_client() # Putting client back in the global scope globals()['client'] = client # run the interpreter console_fn()
def _get_combined_preferences(self): # Get layers of default preferences to match how they are applied # We ignore self_link as we don't want to over-write it defaultpreferences = get_mailman_client().preferences combinedpreferences = {} for key in defaultpreferences: if key != u"self_link": combinedpreferences[key] = defaultpreferences[key] # Clobber defaults with any preferences already set for key in self.mm_user.preferences: if key != u"self_link": combinedpreferences[key] = self.mm_user.preferences[key] return (combinedpreferences)
def subscribe(list_id, user, email=None, display_name=None): if email is None: email = user.email if display_name is None: display_name = "%s %s" % (user.first_name, user.last_name) client = get_mailman_client() rest_list = client.get_list(list_id) subscription_policy = rest_list.settings.get("subscription_policy", "moderate") # Add a flag to return that would tell the user they have been subscribed # to the current list. subscribed_now = False try: member = rest_list.get_member(email) except ValueError: # We don't want to bypass moderation, don't subscribe. Instead # raise an error so that it can be caught to show the user if subscription_policy in ("moderate", "confirm_then_moderate"): raise ModeratedListException( "This list is moderated, please subscribe" " to it before posting.") # not subscribed yet, subscribe the user without email delivery try: member = rest_list.subscribe(email, display_name, pre_verified=True, pre_confirmed=True) except HTTPError as e: if e.code == 409: logger.info("Subscription for %s to %s is already pending", email, list_id) return subscribed_now else: raise # The result can be a Member object or a dict if the subscription can't # be done directly, or if it's pending, or something else. # Broken API :-( if isinstance(member, dict): logger.info("Subscription for %s to %s is pending", email, list_id) return subscribed_now member.preferences["delivery_status"] = "by_user" member.preferences.save() subscribed_now = True cache.delete("User:%s:subscriptions" % user.id, version=2) logger.info("Subscribing %s to %s on first post", email, list_id) return subscribed_now
def set_mailman_id(self): try: client = get_mailman_client() mm_user = client.get_user(self.address) except HTTPError as e: if e.code == 404: return # User not found in Mailman # normalize all possible error types raise MailmanConnectionError(e) except ValueError as e: # This smells like a badly formatted email address (saw it in the # wild) logger.warning( "Invalid response when getting user %s from Mailman", self.address) return # Ignore it self.mailman_id = mm_user.user_id self.save()
def _get_combined_preferences(self): # grab the default preferences defaultpreferences = get_mailman_client().preferences # grab your global preferences globalpreferences = self.mm_user.preferences # start a new combined preferences object combinedpreferences = [] for sub in self.subscriptions: # make a per-address prefs object prefs = {} # initialize with default preferences for key in defaultpreferences: if key != u"self_link": prefs[key] = defaultpreferences[key] # overwrite with user's global preferences for key in globalpreferences: if key != u"self_link": prefs[key] = globalpreferences[key] # overwrite with address-based preferences # There is currently no better way to do this, # we may consider revisiting. addresspreferences = {} for address in self.mm_user.addresses: if sub.email == address.email: addresspreferences = address.preferences for key in addresspreferences: if key != u"self_link": prefs[key] = addresspreferences[key] # overwrite with subscription-specific preferences for key in sub.preferences: if key != u"self_link": prefs[key] = sub.preferences[key] combinedpreferences.append(prefs) return combinedpreferences
def list_index_authenticated(request): """Index page for authenticated users. Index page for authenticated users is slightly different than un-authenticated ones. Authenticated users will see all their memberships in the index page. This view is not paginated and will show all the lists. """ role = request.GET.get('role', None) client = get_mailman_client() choosable_domains = _get_choosable_domains(request) # Get all the verified addresses of the user. user_emails = EmailAddress.objects.filter( user=request.user, verified=True).order_by( "email").values_list("email", flat=True) # Get all the mailing lists for the current user. all_lists = [] for user_email in user_emails: try: all_lists.extend(client.find_lists(user_email, role=role)) except HTTPError: # No lists exist with the given role for the given user. pass # If the user has no list that they are subscriber/owner/moderator of, we # just redirect them to the index page with all lists. if len(all_lists) == 0 and role is None: return redirect(reverse('list_index') + '?all-lists') # Render the list index page. context = { 'lists': _unique_lists(all_lists), 'domain_count': len(choosable_domains), 'role': role } return render( request, 'postorius/index.html', context )
def subscriptions(request): profile = request.user.hyperkitty_profile mm_user_id = get_mailman_user_id(request.user) subs = [] for mlist_id in get_subscriptions(request.user): try: mlist = MailingList.objects.get(list_id=mlist_id) except MailingList.DoesNotExist: mlist = None # no archived email yet posts_count = likes = dislikes = 0 first_post = all_posts_url = None if mlist is not None: list_name = mlist.name posts_count = profile.emails.filter( mailinglist__name=mlist.name).count() likes, dislikes = profile.get_votes_in_list(mlist.name) first_post = profile.get_first_post(mlist) if mm_user_id is not None: all_posts_url = "%s?list=%s" % ( reverse("hk_user_posts", args=[mm_user_id]), mlist.name) else: list_name = get_mailman_client().get_list(mlist_id).fqdn_listname likestatus = "neutral" if likes - dislikes >= 10: likestatus = "likealot" elif likes - dislikes > 0: likestatus = "like" subs.append({ "list_name": list_name, "mlist": mlist, "posts_count": posts_count, "first_post": first_post, "likes": likes, "dislikes": dislikes, "likestatus": likestatus, "all_posts_url": all_posts_url, }) return render(request, 'hyperkitty/user_profile/subscriptions.html', { "subscriptions": subs, "subpage": "subscriptions", })
def get_new_lists_from_mailman(): from hyperkitty.models import MailingList mmclient = get_mailman_client() page_num = 0 while page_num < 10000: # Just for security page_num += 1 try: mlist_page = mmclient.get_list_page(count=10, page=page_num) except MailmanConnectionError: break except HTTPError: break # can't update at this time for mm_list in mlist_page: if MailingList.objects.filter(name=mm_list.fqdn_listname).exists(): continue if mm_list.settings["archive_policy"] == "never": continue # Should we display those lists anyway? logger.info("Imported the new list %s from Mailman", mm_list.fqdn_listname) mlist = MailingList.objects.create(name=mm_list.fqdn_listname) mlist.update_from_mailman() if not mlist_page.has_next: break
def _get_combined_preferences(self): # grab the default preferences defaultpreferences = get_mailman_client().preferences # grab your global preferences globalpreferences = self.mm_user.preferences # start a new combined preferences object combinedpreferences = [] for address in self.mm_user.addresses: # make a per-address prefs object prefs = {} # initialize with default preferences for key in defaultpreferences: if key != u"self_link": prefs[key] = defaultpreferences[key] # overwrite with user's global preferences for key in globalpreferences: if key != u"self_link": prefs[key] = globalpreferences[key] # overwrite with address-specific preferences for key in address.preferences: if key != u"self_link": prefs[key] = address.preferences[key] combinedpreferences.append(prefs) # put the combined preferences back on the original object for key in prefs: if key != u"self_link": address.preferences[key] = prefs[key] return combinedpreferences
def handle(self, *args, **kwargs): client = get_mailman_client() for user in self._get_all_users(client): self._reset_password(user)
def list_member_options(request, list_id, email): template_name = 'postorius/lists/memberoptions.html' client = get_mailman_client() mm_list = List.objects.get_or_404(fqdn_listname=list_id) try: mm_member = client.get_member(list_id, email) member_prefs = mm_member.preferences except ValueError: raise Http404(_('Member does not exist')) except Mailman404Error: return render(request, template_name, {'nolists': 'true'}) initial_moderation = dict([ (key, getattr(mm_member, key)) for key in MemberModeration.base_fields ]) if request.method == 'POST': if request.POST.get("formname") == 'preferences': moderation_form = MemberModeration(initial=initial_moderation) preferences_form = UserPreferences( request.POST, initial=member_prefs) if preferences_form.is_valid(): if not preferences_form.has_changed(): messages.info(request, _("No change to the member's preferences.")) return redirect('list_member_options', list_id, email) for key in preferences_form.fields.keys(): member_prefs[key] = preferences_form.cleaned_data[key] try: member_prefs.save() except HTTPError as e: messages.error(request, e.msg) else: messages.success(request, _("The member's preferences have" " been updated.")) return redirect('list_member_options', list_id, email) elif request.POST.get("formname") == 'moderation': preferences_form = UserPreferences(initial=member_prefs) moderation_form = MemberModeration( request.POST, initial=initial_moderation) if moderation_form.is_valid(): if not moderation_form.has_changed(): messages.info(request, _("No change to the member's moderation.")) return redirect('list_member_options', list_id, email) for key in moderation_form.fields.keys(): setattr(mm_member, key, moderation_form.cleaned_data[key]) try: mm_member.save() except HTTPError as e: messages.error(request, e.msg) else: messages.success(request, _("The member's moderation " "settings have been updated.")) return redirect('list_member_options', list_id, email) else: preferences_form = UserPreferences(initial=member_prefs) moderation_form = MemberModeration(initial=initial_moderation) return render(request, template_name, { 'mm_member': mm_member, 'list': mm_list, 'preferences_form': preferences_form, 'moderation_form': moderation_form, })
def client(self): if getattr(self, '_client', None) is None: self._client = get_mailman_client() return self._client
def setUp(self): self.mm_client = get_mailman_client()
class AlterMessagesForm(ListSettingsForm): """ Alter messages list settings. """ filter_content = forms.TypedChoiceField( choices=((True, _('Yes')), (False, _('No'))), widget=forms.RadioSelect, required=False, label=_('Filter content'), help_text=_('Should Mailman filter the content of list traffic ' 'according to the settings below?')) collapse_alternatives = forms.TypedChoiceField( choices=((True, _('Yes')), (False, _('No'))), widget=forms.RadioSelect, required=False, label=_('Collapse alternatives'), help_text=_('Should Mailman collapse multipart/alternative to ' 'its first part content?')) convert_html_to_plaintext = forms.TypedChoiceField( choices=((True, _('Yes')), (False, _('No'))), widget=forms.RadioSelect, required=False, label=_('Convert html to plaintext'), help_text=_('Should Mailman convert text/html parts to plain text? ' 'This conversion happens after MIME attachments ' 'have been stripped.')) anonymous_list = forms.TypedChoiceField( choices=((True, _('Yes')), (False, _('No'))), widget=forms.RadioSelect, required=False, label=_('Anonymous list'), help_text=_('Hide the sender of a message, ' 'replacing it with the list address ' '(Removes From, Sender and Reply-To fields)')) include_rfc2369_headers = forms.TypedChoiceField( choices=((True, _('Yes')), (False, _('No'))), widget=forms.RadioSelect, required=False, label=_('Include RFC2369 headers'), help_text=_( 'Yes is highly recommended. RFC 2369 defines a set of List-* ' 'headers that are normally added to every message sent to the ' 'list membership. These greatly aid end-users who are using ' 'standards compliant mail readers. They should normally always ' 'be enabled. However, not all mail readers are standards ' 'compliant yet, and if you have a large number of members who are ' 'using non-compliant mail readers, they may be annoyed at these ' 'headers. You should first try to educate your members as to why ' 'these headers exist, and how to hide them in their mail clients. ' 'As a last resort you can disable these headers, but this is not ' 'recommended (and in fact, your ability to disable these headers ' 'may eventually go away).')) allow_list_posts = forms.TypedChoiceField( choices=((True, _('Yes')), (False, _('No'))), widget=forms.RadioSelect, required=False, label=_("Include the list post header"), help_text=_( "This can be set to no for announce lists that do not wish to " "include the List-Post header because posting to the list is " "discouraged.")) reply_to_address = forms.CharField( label=_('Explicit reply-to address'), required=False, help_text=_( 'This option allows admins to set an explicit Reply-to address. ' 'It is only used if the reply-to is set to use an explicitly set ' 'header')) first_strip_reply_to = forms.TypedChoiceField( choices=((True, _('Yes')), (False, _('No'))), widget=forms.RadioSelect, required=False, help_text=_( 'Should any existing Reply-To: header found in the original ' 'message be stripped? If so, this will be done regardless of ' 'whether an explict Reply-To: header is added by Mailman or not.')) reply_goes_to_list = forms.ChoiceField( label=_('Reply goes to list'), widget=forms.Select(), required=False, error_messages={ 'required': _("Please choose a reply-to action.")}, choices=( ('no_munging', _('No Munging')), ('point_to_list', _('Reply goes to list')), ('explicit_header', _('Explicit Reply-to header set')), ('explicit_header_only', _('Explicit Reply-to set; no Cc added'))), help_text=_( 'Where are replies to list messages directed? No Munging is ' 'strongly recommended for most mailing lists. \nThis option ' 'controls what Mailman does to the Reply-To: header in messages ' 'flowing through this mailing list. When set to No Munging, no ' 'Reply-To: header is ' 'added by Mailman, although if one is present in the original ' 'message, it is not stripped. Setting this value to either Reply ' 'to List, Explicit Reply, or Reply Only causes Mailman to insert ' 'a specific Reply-To: header in all messages, overriding the ' 'header in the original message if necessary ' '(Explicit Reply inserts the value of reply_to_address). ' 'Explicit Reply-to set; no Cc added is useful for' 'announce-only lists where you want to avoid someone replying ' 'to the list address. There are many reasons not to introduce or ' 'override the Reply-To: header. One is that some posters depend ' 'on their own Reply-To: settings to convey their valid return ' 'address. Another is that modifying Reply-To: makes it much more ' 'difficult to send private replies. See `Reply-To\' Munging ' 'Considered Harmful for a general discussion of this issue. ' 'See Reply-To Munging Considered Useful for a dissenting opinion. ' 'Some mailing lists have restricted ' 'posting privileges, with a parallel list devoted to discussions. ' 'Examples are `patches\' or `checkin\' lists, where software ' 'changes are posted by a revision control system, but discussion ' 'about the changes occurs on a developers mailing list. To ' 'support these types of mailing lists, select Explicit Reply and ' 'set the Reply-To: address option to point to the parallel list.')) posting_pipeline = forms.ChoiceField( label=_('Pipeline'), widget=forms.Select(), required=False, choices=lambda: ((p, p) for p in get_mailman_client() .pipelines['pipelines']), help_text=_('Type of pipeline you want to use for this mailing list'))
def _get_list_page(count, page): client = get_mailman_client() advertised = not request.user.is_superuser return client.get_list_page( advertised=advertised, count=count, page=page)