Skip to content

Commit

Permalink
Rework tagging infrastructure
Browse files Browse the repository at this point in the history
Solve getpatchwork#113 and getpatchwork#57 GitHub issues, fix up returning tags in the API,
keep track of tag origin to later be able to add tags to comments in
the API.

Use relations Tag-Patch and Tag-CoverLetter to avoid duplication of
tags for each patch in series, and use `series` attribute of
SubmissionTag as a notion of a tag which is related to each patch in the
series (because it comes from cover letter or it's comments)

Signed-off-by: Veronika Kabatova <vkabatov@redhat.com>
  • Loading branch information
veruu committed May 25, 2018
1 parent d0ea6fa commit 2b75540
Show file tree
Hide file tree
Showing 11 changed files with 354 additions and 131 deletions.
9 changes: 5 additions & 4 deletions docs/usage/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,11 @@ one delegate can be assigned to a patch.
Tags
~~~~

Tags are specially formatted metadata appended to the foot the body of a patch
or a comment on a patch. Patchwork extracts these tags at parse time and
associates them with the patch. You add extra tags to an email by replying to
the email. The following tags are available on a standard Patchwork install:
Tags are specially formatted metadata appended to the foot the body of a patch,
cover letter or a comment related to them. Patchwork extracts these tags at
parse time and associates them with patches. You add extra tags to an email by
replying to the email. The following tags are available on a standard Patchwork
install:

``Acked-by:``
For example::
Expand Down
18 changes: 17 additions & 1 deletion patchwork/api/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,34 @@
from patchwork.api.base import PatchworkPermission
from patchwork.api.embedded import PersonSerializer
from patchwork.models import Comment
from patchwork.models import SubmissionTag


class CommentListSerializer(BaseHyperlinkedModelSerializer):

subject = SerializerMethodField()
headers = SerializerMethodField()
submitter = PersonSerializer(read_only=True)
tags = SerializerMethodField()

def get_subject(self, comment):
return email.parser.Parser().parsestr(comment.headers,
True).get('Subject', '')

def get_tags(self, instance):
if not instance.submission.project.use_tags:
return {}

tags = SubmissionTag.objects.filter(
comment=instance
).values_list('tag__name', 'value')

result = {}
for name, value in tags:
result.setdefault(name, []).append(value)

return result

def get_headers(self, comment):
headers = {}

Expand All @@ -55,7 +71,7 @@ def get_headers(self, comment):
class Meta:
model = Comment
fields = ('id', 'msgid', 'date', 'subject', 'submitter', 'content',
'headers')
'headers', 'tags')
read_only_fields = fields


Expand Down
19 changes: 17 additions & 2 deletions patchwork/api/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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(BaseHyperlinkedModelSerializer):
Expand All @@ -39,6 +40,7 @@ class CoverLetterListSerializer(BaseHyperlinkedModelSerializer):
mbox = SerializerMethodField()
series = SeriesSerializer(many=True, read_only=True)
comments = SerializerMethodField()
tags = SerializerMethodField()

def get_mbox(self, instance):
request = self.context.get('request')
Expand All @@ -48,13 +50,26 @@ def get_comments(self, cover):
return self.context.get('request').build_absolute_uri(
reverse('api-cover-comment-list', kwargs={'pk': cover.id}))

def get_tags(self, instance):
if not instance.project.use_tags:
return {}

tags = SubmissionTag.objects.filter(
submission=instance).values_list('tag__name', 'value').distinct()

result = {}
for name, value in tags:
result.setdefault(name, []).append(value)

return result

class Meta:
model = CoverLetter
fields = ('id', 'url', 'project', 'msgid', 'date', 'name', 'submitter',
'mbox', 'series', 'comments')
'mbox', 'series', 'comments', 'tags')
read_only_fields = fields
versioned_fields = {
'1.1': ('mbox', 'comments'),
'1.1': ('mbox', 'comments', 'tags'),
}
extra_kwargs = {
'url': {'view_name': 'api-cover-detail'},
Expand Down
68 changes: 66 additions & 2 deletions patchwork/api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
from django.core.exceptions import ValidationError
from django.db.models import Q
from django_filters.rest_framework import FilterSet
from django_filters import Filter
from django_filters import IsoDateTimeFilter
from django_filters import ModelMultipleChoiceFilter
from django.forms import ModelMultipleChoiceField as BaseMultipleChoiceField
from django.forms.widgets import HiddenInput
from django.forms.widgets import MultipleHiddenInput

from patchwork.models import Bundle
Expand All @@ -35,6 +37,7 @@
from patchwork.models import Project
from patchwork.models import Series
from patchwork.models import State
from patchwork.models import SubmissionTag


# custom fields, filters
Expand Down Expand Up @@ -136,6 +139,60 @@ class StateFilter(ModelMultipleChoiceFilter):
field_class = StateChoiceField


class TagNameFilter(Filter):

def filter(self, qs, tag_name):
submissions_series = SubmissionTag.objects.filter(
tag__name__iexact=tag_name
).values_list('submission__id', 'series')

submission_list = []
series_list = []
for submission, series in submissions_series:
submission_list.append(submission)
series_list.append(series)

return qs.filter(Q(id__in=submission_list) | Q(series__in=series_list))


class TagValueFilter(Filter):

def filter(self, qs, tag_value):
submissions_series = SubmissionTag.objects.filter(
value__icontains=tag_value
).values_list('submission__id', 'series')

submission_list = []
series_list = []
for submission, series in submissions_series:
submission_list.append(submission)
series_list.append(series)

return qs.filter(Q(id__in=submission_list) | Q(series__in=series_list))


class TagFilter(Filter):

def filter(self, qs, query):
try:
tag_name, tag_value = query.split(',', 1)
except ValueError:
raise ValidationError('Query in format `tag=name,value` expected!')

submissions_series = SubmissionTag.objects.filter(
tag__name__iexact=tag_name,
value__icontains=tag_value
).values_list('submission__id', 'series')

submission_list = []
series_list = []
for submission, series in submissions_series:
submission_list.append(submission)
series_list.append(series)

return qs.filter(Q(id__in=submission_list) | Q(series__in=series_list))


class UserChoiceField(ModelMultipleChoiceField):

alternate_lookup = 'username__iexact'
Expand Down Expand Up @@ -173,10 +230,14 @@ class CoverLetterFilterSet(TimestampMixin, FilterSet):
series = BaseFilter(queryset=Project.objects.all(),
widget=MultipleHiddenInput)
submitter = PersonFilter(queryset=Person.objects.all())
tagname = TagNameFilter(label='Tag name')
tagvalue = TagValueFilter(widget=HiddenInput)
tag = TagFilter(widget=HiddenInput)

class Meta:
model = CoverLetter
fields = ('project', 'series', 'submitter')
fields = ('project', 'series', 'submitter', 'tagname', 'tagvalue',
'tag')


class PatchFilterSet(TimestampMixin, FilterSet):
Expand All @@ -189,11 +250,14 @@ class PatchFilterSet(TimestampMixin, FilterSet):
submitter = PersonFilter(queryset=Person.objects.all())
delegate = UserFilter(queryset=User.objects.all())
state = StateFilter(queryset=State.objects.all())
tagname = TagNameFilter(label='Tag name')
tagvalue = TagValueFilter(widget=HiddenInput)
tag = TagFilter(widget=HiddenInput)

class Meta:
model = Patch
fields = ('project', 'series', 'submitter', 'delegate',
'state', 'archived')
'state', 'archived', 'tagname', 'tagvalue', 'tag')


class CheckFilterSet(TimestampMixin, FilterSet):
Expand Down
21 changes: 18 additions & 3 deletions patchwork/api/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import email.parser

from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from rest_framework.generics import ListAPIView
from rest_framework.generics import RetrieveUpdateAPIView
Expand All @@ -35,6 +36,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 @@ -104,9 +106,18 @@ def get_checks(self, instance):
reverse('api-check-list', kwargs={'patch_id': instance.id}))

def get_tags(self, instance):
# TODO(stephenfin): Make tags performant, possibly by reworking the
# model
return {}
if not instance.project.use_tags:
return {}

tags = SubmissionTag.objects.filter(
Q(submission=instance) | Q(series=instance.series.all())
).values_list('tag__name', 'value').distinct()

result = {}
for name, value in tags:
result.setdefault(name, []).append(value)

return result

class Meta:
model = Patch
Expand All @@ -123,6 +134,9 @@ class Meta:
extra_kwargs = {
'url': {'view_name': 'api-patch-detail'},
}
versioned_fields = {
'1.1': ('tags', ),
}


class PatchDetailSerializer(PatchListSerializer):
Expand Down Expand Up @@ -155,6 +169,7 @@ class Meta:
'headers', 'content', 'diff', 'prefixes')
versioned_fields = PatchListSerializer.Meta.versioned_fields
extra_kwargs = PatchListSerializer.Meta.extra_kwargs
versioned_fields = PatchListSerializer.Meta.versioned_fields


class PatchList(ListAPIView):
Expand Down
16 changes: 14 additions & 2 deletions patchwork/management/commands/retag.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@

from django.core.management.base import BaseCommand

from patchwork.models import Cover
from patchwork.models import Patch
from patchwork.models import SeriesPatch


class Command(BaseCommand):
help = 'Update the tag (Ack/Review/Test) counts on existing patches'
help = 'Update tags on existing patches'
args = '[<patch_id>...]'

def handle(self, *args, **options):
Expand All @@ -37,7 +39,17 @@ def handle(self, *args, **options):
count = query.count()

for i, patch in enumerate(query.iterator()):
patch.refresh_tag_counts()
patch.refresh_tags()
for comment in patch.comments.all():
comment.refresh_tags()

series_patches = SeriesPatch.objects.filter(patch_id=patch.id)
for series_patch in series_patches:
cover = series_patch.series.cover_letter
cover.refresh_tags()
for comment in cover.comments.all():
comment.refresh_tags()

if (i % 10) == 0:
self.stdout.write('%06d/%06d\r' % (i, count), ending='')
self.stdout.flush()
Expand Down
Loading

0 comments on commit 2b75540

Please sign in to comment.