Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Archweb provides multiple management commands for importing various sorts of dat
* rematch_developers - Rematch flag requests and packages where user_id/packager_id is NULL to a Developer.
* reporead - Parses a repo.db.tar.gz, repo.files.tar.gz file and updates the Arch database with the relevant changes.
* reporead_inotify - Watches a templated patch for updates of *.files.tar.gz to update Arch databases with.
* donor_import - Import donators from a dovecot maildir dump.
* donor_import - Import a single donator from a mail passed to stdin
* mirrorcheck - Poll every active mirror URLs to store the lastsnyc time and record network timing details.
* mirrorresolv - Poll every active mirror URLs and determine wheteher they have IP4 and/or IPv6 addresses.
* populate_signoffs - retrieves the latest commit message of a signoff-eligible package.
Expand Down
65 changes: 31 additions & 34 deletions main/management/commands/donor_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@

Subject: Receipt [$25.00] By: John Doe [john.doe@archlinux.org]

Usage: ./manage.py donor_import path/to/maildir/
Usage: cat mail.eml| ./manage.py donor_import
Usage: As it takes an email on stdin, this can be used as a mda command in e.g. fetchmail.
"""

import codecs
import logging
import mailbox
import email
import sys

from email.header import decode_header

from parse import parse
from argparse import FileType

from django.db.utils import Error as DBError
from django.core.management.base import BaseCommand, CommandError
Expand All @@ -37,9 +39,8 @@


class Command(BaseCommand):

def add_arguments(self, parser):
parser.add_argument('maildir', type=str)
parser.add_argument('input', nargs='?', type=FileType('r'), default=sys.stdin)

def decode_subject(self, subject):
subject = decode_header(subject)
Expand Down Expand Up @@ -84,34 +85,30 @@ def handle(self, *args, **options):
elif v >= 2:
logger.level = logging.DEBUG

msg = email.message_from_file(options['input'])
if not msg['subject']:
raise CommandError(u"Failed to read from STDIN")
subject = msg.get('subject', '')
if 'utf-8' in subject:
# Decode UTF-8 encoded subjects
subject = self.decode_subject(subject)

# Subject header can contain enters, replace them with a space
subject = subject.replace(u'\n', u' ')

name = self.parse_subject(subject)
if not name:
logger.error(u'Unable to parse: %s', subject)
sys.exit(0)

name = self.sanitize_name(name)
if not name:
logger.error(u'Invalid name in subject: %s', subject)
sys.exit(0)

try:
directory = options['maildir']
maildir = mailbox.Maildir(directory, create=False)
except mailbox.Error:
raise CommandError(u"Failed to open maildir")

for msg in maildir:
subject = msg.get('subject', '')
if 'utf-8' in subject:
# Decode UTF-8 encoded subjects
subject = self.decode_subject(subject)

# Subject header can contain enters, replace them with a space
subject = subject.replace(u'\n', u' ')

name = self.parse_subject(subject)
if not name:
logger.error(u'Unable to parse: %s', subject)
continue

name = self.sanitize_name(name)
if not name:
logger.error(u'Invalid name in subject: %s', subject)
continue

try:
_, created = Donor.objects.get_or_create(name=name)
if created:
logger.info(u'Adding donor: {}'.format(name))
except DBError as e:
logger.info(u'Error while adding donor: %s, %s', name, e)
_, created = Donor.objects.get_or_create(name=name)
if created:
logger.info(u'Adding donor: {}'.format(name))
except DBError as e:
logger.info(u'Error while adding donor: %s, %s', name, e)
28 changes: 13 additions & 15 deletions main/tests/test_donor_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

from email.header import Header
from email.message import Message
from mailbox import Maildir
from tempfile import mkdtemp
from shutil import rmtree

from django.test import TransactionTestCase
from django.core.management import call_command
Expand Down Expand Up @@ -41,37 +39,37 @@ def test_decode_subject(self):
def test_invalid_args(self):
with self.assertRaises(CommandError) as e:
call_command('donor_import')
self.assertIn('Error: the following arguments are required', str(e.exception))
self.assertIn('Failed to read from STDIN', str(e.exception))

def test_invalid_path(self):
with self.assertRaises(CommandError) as e:
call_command('donor_import', '/tmp/non-existant')
self.assertIn('Failed to open maildir', str(e.exception))
self.assertIn('argument input: can\'t open', str(e.exception))

def test_maildir(self):
tmpdir = mkdtemp('archweb')
mdir = tmpdir + '/maildir'
def test_import(self):
tmpmail = mkdtemp('archweb') + "/mail"

maildir = Maildir(mdir)
msg = Message()
msg['subject'] = 'John Doe'
msg['to'] = 'John Doe <john@doe.com>'
maildir.add(msg)
with open(tmpmail, 'wb') as fp:
fp.write(msg.as_bytes())

# Invalid
call_command('donor_import', mdir)
with self.assertRaises(SystemExit):
call_command('donor_import', tmpmail)
self.assertEqual(len(Donor.objects.all()), 0)

# Valid
msg = Message()
msg['subject'] = 'Receipt [$25.00] By: David Doe [david@doe.com]'
msg['to'] = 'John Doe <david@doe.com>'
maildir.add(msg)
call_command('donor_import', mdir)
with open(tmpmail, 'wb') as fp:
fp.write(msg.as_bytes())

call_command('donor_import', tmpmail)
self.assertEqual(len(Donor.objects.all()), 1)

# Re-running should result in no new donor
call_command('donor_import', mdir)
call_command('donor_import', tmpmail)
self.assertEqual(len(Donor.objects.all()), 1)

rmtree(tmpdir)