[PATCH 3/4] parser: allow to handle multiple projects under the same list
Philippe Pepiot
phil at philpep.org
Sun May 28 04:17:55 AEST 2017
By adding a `subject_prefix` settings to Project. Mail will be assigned
to project if List-Id match and prefix is present.
Signed-off-by: Philippe Pepiot <phil at philpep.org>
---
patchwork/migrations/0020_auto_20170527_1119.py | 37 ++++++++++++++++++++
patchwork/models.py | 6 +++-
patchwork/parser.py | 36 +++++++++++++-------
patchwork/tests/test_parser.py | 45 +++++++++++++++++++++----
4 files changed, 104 insertions(+), 20 deletions(-)
create mode 100644 patchwork/migrations/0020_auto_20170527_1119.py
diff --git a/patchwork/migrations/0020_auto_20170527_1119.py b/patchwork/migrations/0020_auto_20170527_1119.py
new file mode 100644
index 0000000..59165e3
--- /dev/null
+++ b/patchwork/migrations/0020_auto_20170527_1119.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-05-27 11:19
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('patchwork', '0019_userprofile_show_ids'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='patch',
+ options={'base_manager_name': 'objects', 'verbose_name_plural': 'Patches'},
+ ),
+ migrations.AlterModelOptions(
+ name='series',
+ options={'ordering': ('date',), 'verbose_name_plural': 'Series'},
+ ),
+ migrations.AddField(
+ model_name='project',
+ name='subject_prefix',
+ field=models.CharField(blank=True, help_text=b'An optional prefix that mail subject must match. This allow to handle multiple project under the same list ID', max_length=255),
+ ),
+ migrations.AlterField(
+ model_name='project',
+ name='listid',
+ field=models.CharField(max_length=255),
+ ),
+ migrations.AlterUniqueTogether(
+ name='project',
+ unique_together=set([('listid', 'subject_prefix')]),
+ ),
+ ]
diff --git a/patchwork/models.py b/patchwork/models.py
index ee66ace..148c1f7 100644
--- a/patchwork/models.py
+++ b/patchwork/models.py
@@ -67,8 +67,11 @@ class Project(models.Model):
linkname = models.CharField(max_length=255, unique=True)
name = models.CharField(max_length=255, unique=True)
- listid = models.CharField(max_length=255, unique=True)
+ listid = models.CharField(max_length=255)
listemail = models.CharField(max_length=200)
+ subject_prefix = models.CharField(max_length=255, blank=True, help_text=(
+ 'An optional prefix that mail subject must match. This allow to '
+ 'handle multiple project under the same list ID'))
# url metadata
@@ -97,6 +100,7 @@ class Project(models.Model):
class Meta:
ordering = ['linkname']
+ unique_together = (('listid', 'subject_prefix'))
@python_2_unicode_compatible
diff --git a/patchwork/parser.py b/patchwork/parser.py
index 6d4640a..e5af55b 100644
--- a/patchwork/parser.py
+++ b/patchwork/parser.py
@@ -136,17 +136,26 @@ def clean_header(header):
return normalise_space(header_str)
-def find_project_by_id(list_id):
- """Find a `project` object with given `list_id`."""
- project = None
- try:
- project = Project.objects.get(listid=list_id)
- except Project.DoesNotExist:
- pass
- return project
+def find_project_by_id(list_id, prefixes):
+ """Find a `project` object with given `list_id` and optionally matching
+ given `prefixes`.
+
+ If no prefixes matches, return the project with blank prefix.
+ If multiple prefixes matches, display a warning and return the project
+ matching the first prefix.
+ """
+ prefixes = prefixes + ['']
+ projects = Project.objects.filter(listid=list_id,
+ subject_prefix__in=prefixes)
+ projects = sorted(projects, key=lambda p: prefixes.index(p.subject_prefix))
+ if not projects:
+ return
+ if len([p for p in projects if p.subject_prefix != '']) > 1:
+ logger.warning('Find multiple projects matching prefixes %s', prefixes)
+ return projects[0]
-def find_project_by_header(mail):
+def find_project_by_header(mail, prefixes):
project = None
listid_res = [re.compile(r'.*<([^>]+)>.*', re.S),
re.compile(r'^([\S]+)$', re.S)]
@@ -164,7 +173,7 @@ def find_project_by_header(mail):
listid = match.group(1)
- project = find_project_by_id(listid)
+ project = find_project_by_id(listid, prefixes)
if project:
break
@@ -789,10 +798,12 @@ def parse_mail(mail, list_id=None):
logger.debug("Ignoring email due to 'ignore' hint")
return
+ subject = mail.get('Subject')
+ prefixes = clean_subject(subject)[1]
if list_id:
- project = find_project_by_id(list_id)
+ project = find_project_by_id(list_id, prefixes)
else:
- project = find_project_by_header(mail)
+ project = find_project_by_header(mail, prefixes)
if project is None:
logger.error('Failed to find a project for email')
@@ -802,7 +813,6 @@ def parse_mail(mail, list_id=None):
msgid = mail.get('Message-Id').strip()
author = find_author(mail)
- subject = mail.get('Subject')
name, prefixes = clean_subject(subject, [project.linkname])
is_comment = subject_check(subject)
x, n = parse_series_marker(prefixes)
diff --git a/patchwork/tests/test_parser.py b/patchwork/tests/test_parser.py
index 63231fa..0430f52 100644
--- a/patchwork/tests/test_parser.py
+++ b/patchwork/tests/test_parser.py
@@ -477,6 +477,39 @@ class MultipleProjectPatchCommentTest(MultipleProjectPatchTest):
Comment.objects.filter(submission=patch).count(), 1)
+class MultipleProjectSameListTest(TestCase):
+ """Test that multiple projects can be handled by the same mailing-list with
+ `subject_prefix` setting"""
+
+ def setUp(self):
+ self.p1 = create_project(listid='test.example.com')
+ self.p2 = create_project(listid='test.example.com',
+ subject_prefix='foo')
+ self.p3 = create_project(listid='test.example.com',
+ subject_prefix='bar')
+
+ @staticmethod
+ def _parse_mail(subject):
+ email = create_email(content=SAMPLE_DIFF, subject=subject)
+ return parse_mail(email)
+
+ def test_prefix_matching(self):
+ patch = self._parse_mail('[PATCH foo foobar] foo bar')
+ self.assertEqual(patch.project, self.p2)
+ patch = self._parse_mail('[PATCH foobar bar] foo bar')
+ self.assertEqual(patch.project, self.p3)
+
+ def test_prefix_not_matching(self):
+ patch = self._parse_mail('[PATCH foobar] foo bar')
+ self.assertEqual(patch.project, self.p1)
+
+ def test_multiple_matching(self):
+ patch = self._parse_mail('[PATCH foo foobar bar] meep')
+ self.assertEqual(patch.project, self.p2)
+ patch = self._parse_mail('[PATCH bar foobar foo] meep')
+ self.assertEqual(patch.project, self.p3)
+
+
class ListIdHeaderTest(TestCase):
"""Test that we parse List-Id headers from mails correctly."""
@@ -485,25 +518,25 @@ class ListIdHeaderTest(TestCase):
def test_no_list_id(self):
email = MIMEText('')
- project = find_project_by_header(email)
+ project = find_project_by_header(email, [])
self.assertEqual(project, None)
def test_blank_list_id(self):
email = MIMEText('')
email['List-Id'] = ''
- project = find_project_by_header(email)
+ project = find_project_by_header(email, [])
self.assertEqual(project, None)
def test_whitespace_list_id(self):
email = MIMEText('')
email['List-Id'] = ' '
- project = find_project_by_header(email)
+ project = find_project_by_header(email, [])
self.assertEqual(project, None)
def test_substring_list_id(self):
email = MIMEText('')
email['List-Id'] = 'example.com'
- project = find_project_by_header(email)
+ project = find_project_by_header(email, [])
self.assertEqual(project, None)
def test_short_list_id(self):
@@ -511,13 +544,13 @@ class ListIdHeaderTest(TestCase):
is only the list ID itself (without enclosing angle-brackets). """
email = MIMEText('')
email['List-Id'] = self.project.listid
- project = find_project_by_header(email)
+ project = find_project_by_header(email, [])
self.assertEqual(project, self.project)
def test_long_list_id(self):
email = MIMEText('')
email['List-Id'] = 'Test text <%s>' % self.project.listid
- project = find_project_by_header(email)
+ project = find_project_by_header(email, [])
self.assertEqual(project, self.project)
--
2.1.4
More information about the Patchwork
mailing list