Skip to content

Commit

Permalink
Implementation of tagging for cover letters
Browse files Browse the repository at this point in the history
Solves getpatchwork#113 and partially getpatchwork#57 GitHub issues.

* Change PatchTag to SubmissionTag to allow tagging of cover letters
* Make CoverLetter tags increase tag counts on all patches in the
  series

Signed-off-by: Veronika Kabatova <vkabatov@redhat.com>
  • Loading branch information
veruu committed Feb 20, 2018
1 parent 83d52c0 commit 7ebadc3
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 58 deletions.
12 changes: 11 additions & 1 deletion patchwork/api/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from patchwork.api.embedded import ProjectSerializer
from patchwork.api.embedded import SeriesSerializer
from patchwork.models import CoverLetter
from patchwork.models import SubmissionTag


class CoverLetterListSerializer(HyperlinkedModelSerializer):
Expand All @@ -37,15 +38,24 @@ class CoverLetterListSerializer(HyperlinkedModelSerializer):
submitter = PersonSerializer(read_only=True)
mbox = SerializerMethodField()
series = SeriesSerializer(many=True, read_only=True)
tags = SerializerMethodField()

def get_mbox(self, instance):
request = self.context.get('request')
return request.build_absolute_uri(instance.get_mbox_url())

def get_tags(self, instance):
tag_ids = [tag.id for tag in instance.project.tags]
return {submissiontag.tag.name: submissiontag.count for
submissiontag in SubmissionTag.objects.filter(
submission_id=instance.id,
tag_id__in=tag_ids
)}

class Meta:
model = CoverLetter
fields = ('id', 'url', 'project', 'msgid', 'date', 'name', 'submitter',
'mbox', 'series')
'mbox', 'series', 'tags')
read_only_fields = fields
extra_kwargs = {
'url': {'view_name': 'api-cover-detail'},
Expand Down
10 changes: 7 additions & 3 deletions patchwork/api/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from patchwork.api.embedded import UserSerializer
from patchwork.models import Patch
from patchwork.models import State
from patchwork.models import SubmissionTag
from patchwork.parser import clean_subject


Expand Down Expand Up @@ -92,9 +93,12 @@ def get_mbox(self, instance):
return request.build_absolute_uri(instance.get_mbox_url())

def get_tags(self, instance):
# TODO(stephenfin): Make tags performant, possibly by reworking the
# model
return {}
tag_ids = [tag.id for tag in instance.project.tags]
return {submissiontag.tag.name: submissiontag.count for
submissiontag in SubmissionTag.objects.filter(
submission_id=instance.id,
tag_id__in=tag_ids
)}

def get_check(self, instance):
return instance.combined_check_state
Expand Down
130 changes: 85 additions & 45 deletions patchwork/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,13 @@ class Meta:
ordering = ['abbrev']


class PatchTag(models.Model):
patch = models.ForeignKey('Patch', on_delete=models.CASCADE)
class SubmissionTag(models.Model):
submission = models.ForeignKey('Submission', on_delete=models.CASCADE)
tag = models.ForeignKey('Tag', on_delete=models.CASCADE)
count = models.IntegerField(default=1)

class Meta:
unique_together = [('patch', 'tag')]
unique_together = [('submission', 'tag')]


def get_default_initial_patch_state():
Expand Down Expand Up @@ -289,10 +289,10 @@ def with_tag_counts(self, project=None):
for tag in tags:
select[tag.attr_name] = (
"coalesce("
"(SELECT count FROM patchwork_patchtag"
" WHERE patchwork_patchtag.patch_id="
"(SELECT count FROM patchwork_submissiontag"
" WHERE patchwork_submissiontag.submission_id="
"patchwork_patch.submission_ptr_id"
" AND patchwork_patchtag.tag_id=%s), 0)")
" AND patchwork_submissiontag.tag_id=%s), 0)")
select_params.append(tag.id)

return qs.extra(select=select, select_params=select_params)
Expand Down Expand Up @@ -367,9 +367,86 @@ class Submission(FilenameMixin, EmailMixin, models.Model):
# submission metadata

name = models.CharField(max_length=255)
submission_tags = models.ManyToManyField(Tag, through=SubmissionTag)

# patchwork metadata

@staticmethod
def extract_tags(content, tags):
counts = Counter()

for tag in tags:
regex = re.compile(tag.pattern, re.MULTILINE | re.IGNORECASE)
counts[tag] = len(regex.findall(content))

return counts

def _set_tag(self, tag, count):
if count == 0:
self.submissiontag_set.filter(tag=tag).delete()
return
submissiontag, _ = SubmissionTag.objects.get_or_create(submission=self,
tag=tag)
if submissiontag.count != count:
submissiontag.count = count
submissiontag.save()

def refresh_tag_counts(self):
tags = self.project.tags
counter = Counter()

if self.content:
counter += self.extract_tags(self.content, tags)

for comment in self.comments.all():
counter = counter + self.extract_tags(comment.content, tags)

# We need to find the series submission belongs to first
related_series = None
refs = [hdr.split(': ')[1] for hdr in self.headers.split('\n') if
hdr.split(': ')[0] == 'In-Reply-To' or
hdr.split(': ')[0] == 'References'] + [self.msgid]
for ref in refs:
try:
related_series = SeriesReference.objects.get(
msgid=ref,
series__project=self.project
).series
except SeriesReference.DoesNotExist:
continue

if related_series:
if hasattr(self, 'coverletter'):
# We tagged cover letter and need to update all patches in
# the series
for patch in related_series.patches.all():
patch_counter = counter

if patch.content:
patch_counter += patch.extract_tags(patch.content,
tags)

for comment in patch.comments.all():
patch_counter += patch.extract_tags(comment.content,
tags)
for tag in tags:
patch._set_tag(tag, patch_counter[tag])
else:
# We tagged a patch but need to take into account all tags on
# the cover letter too
cover = related_series.cover_letter
if cover:
counter += cover.extract_tags(cover.content, tags)
for comment in cover.comments.all():
counter += cover.extract_tags(comment.content, tags)

for tag in tags:
self._set_tag(tag, counter[tag])

def save(self, *args, **kwargs):
super(Submission, self).save(**kwargs)
self.refresh_tag_counts()

def is_editable(self, user):
return False

Expand Down Expand Up @@ -413,7 +490,6 @@ class Patch(SeriesMixin, Submission):
diff = models.TextField(null=True, blank=True)
commit_ref = models.CharField(max_length=255, null=True, blank=True)
pull_url = models.CharField(max_length=255, null=True, blank=True)
tags = models.ManyToManyField(Tag, through=PatchTag)

# patchwork metadata

Expand All @@ -425,38 +501,6 @@ class Patch(SeriesMixin, Submission):

objects = PatchManager()

@staticmethod
def extract_tags(content, tags):
counts = Counter()

for tag in tags:
regex = re.compile(tag.pattern, re.MULTILINE | re.IGNORECASE)
counts[tag] = len(regex.findall(content))

return counts

def _set_tag(self, tag, count):
if count == 0:
self.patchtag_set.filter(tag=tag).delete()
return
patchtag, _ = PatchTag.objects.get_or_create(patch=self, tag=tag)
if patchtag.count != count:
patchtag.count = count
patchtag.save()

def refresh_tag_counts(self):
tags = self.project.tags
counter = Counter()

if self.content:
counter += self.extract_tags(self.content, tags)

for comment in self.comments.all():
counter = counter + self.extract_tags(comment.content, tags)

for tag in tags:
self._set_tag(tag, counter[tag])

def save(self, *args, **kwargs):
if not hasattr(self, 'state') or not self.state:
self.state = get_default_initial_patch_state()
Expand All @@ -466,8 +510,6 @@ def save(self, *args, **kwargs):

super(Patch, self).save(**kwargs)

self.refresh_tag_counts()

def is_editable(self, user):
if not is_authenticated(user):
return False
Expand Down Expand Up @@ -591,13 +633,11 @@ class Comment(EmailMixin, models.Model):

def save(self, *args, **kwargs):
super(Comment, self).save(*args, **kwargs)
if hasattr(self.submission, 'patch'):
self.submission.patch.refresh_tag_counts()
self.submission.refresh_tag_counts()

def delete(self, *args, **kwargs):
super(Comment, self).delete(*args, **kwargs)
if hasattr(self.submission, 'patch'):
self.submission.patch.refresh_tag_counts()
self.submission.refresh_tag_counts()

class Meta:
ordering = ['date']
Expand Down
12 changes: 6 additions & 6 deletions patchwork/tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,11 +783,11 @@ def setUp(self):
def test_tags(self):
self.assertEqual(Patch.objects.count(), 1)
patch = Patch.objects.all()[0]
self.assertEqual(patch.patchtag_set.filter(
self.assertEqual(patch.submissiontag_set.filter(
tag__name='Acked-by').count(), 0)
self.assertEqual(patch.patchtag_set.get(
self.assertEqual(patch.submissiontag_set.get(
tag__name='Reviewed-by').count, 1)
self.assertEqual(patch.patchtag_set.get(
self.assertEqual(patch.submissiontag_set.get(
tag__name='Tested-by').count, 1)


Expand All @@ -811,11 +811,11 @@ def setUp(self):
def test_tags(self):
self.assertEqual(Patch.objects.count(), 1)
patch = Patch.objects.all()[0]
self.assertEqual(patch.patchtag_set.filter(
self.assertEqual(patch.submissiontag_set.filter(
tag__name='Acked-by').count(), 0)
self.assertEqual(patch.patchtag_set.get(
self.assertEqual(patch.submissiontag_set.get(
tag__name='Reviewed-by').count, 1)
self.assertEqual(patch.patchtag_set.get(
self.assertEqual(patch.submissiontag_set.get(
tag__name='Tested-by').count, 1)


Expand Down
6 changes: 3 additions & 3 deletions patchwork/tests/test_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from django.test import TransactionTestCase

from patchwork.models import Patch
from patchwork.models import PatchTag
from patchwork.models import SubmissionTag
from patchwork.models import Tag
from patchwork.tests.utils import create_comment
from patchwork.tests.utils import create_patch
Expand Down Expand Up @@ -97,8 +97,8 @@ def assertTagsEqual(self, patch, acks, reviews, tests): # noqa

def count(name):
try:
return patch.patchtag_set.get(tag__name=name).count
except PatchTag.DoesNotExist:
return patch.submissiontag_set.get(tag__name=name).count
except SubmissionTag.DoesNotExist:
return 0

counts = (
Expand Down

0 comments on commit 7ebadc3

Please sign in to comment.