aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--patchwork/admin.py26
-rw-r--r--patchwork/api/comment.py52
-rw-r--r--patchwork/api/cover.py10
-rw-r--r--patchwork/api/embedded.py2
-rw-r--r--patchwork/api/filters.py6
-rw-r--r--patchwork/management/commands/parsearchive.py11
-rw-r--r--patchwork/migrations/0042_add_cover_model.py247
-rw-r--r--patchwork/models.py120
-rw-r--r--patchwork/parser.py81
-rw-r--r--patchwork/signals.py4
-rw-r--r--patchwork/tests/api/test_comment.py11
-rw-r--r--patchwork/tests/test_detail.py31
-rw-r--r--patchwork/tests/test_mboxviews.py14
-rw-r--r--patchwork/tests/test_parser.py10
-rw-r--r--patchwork/tests/test_series.py2
-rw-r--r--patchwork/tests/test_tags.py6
-rw-r--r--patchwork/tests/utils.py32
-rw-r--r--patchwork/urls.py4
-rw-r--r--patchwork/views/comment.py43
-rw-r--r--patchwork/views/cover.py12
-rw-r--r--patchwork/views/patch.py7
-rw-r--r--patchwork/views/utils.py17
22 files changed, 600 insertions, 148 deletions
diff --git a/patchwork/admin.py b/patchwork/admin.py
index fb79808..a650222 100644
--- a/patchwork/admin.py
+++ b/patchwork/admin.py
@@ -10,10 +10,11 @@ from django.db.models import Prefetch
from patchwork.models import Bundle
from patchwork.models import Check
-from patchwork.models import Comment
-from patchwork.models import CoverLetter
+from patchwork.models import Cover
+from patchwork.models import CoverComment
from patchwork.models import DelegationRule
from patchwork.models import Patch
+from patchwork.models import PatchComment
from patchwork.models import PatchRelation
from patchwork.models import Person
from patchwork.models import Project
@@ -75,14 +76,14 @@ class StateAdmin(admin.ModelAdmin):
admin.site.register(State, StateAdmin)
-class CoverLetterAdmin(admin.ModelAdmin):
+class CoverAdmin(admin.ModelAdmin):
list_display = ('name', 'submitter', 'project', 'date')
list_filter = ('project', )
search_fields = ('name', 'submitter__name', 'submitter__email')
date_hierarchy = 'date'
-admin.site.register(CoverLetter, CoverLetterAdmin)
+admin.site.register(Cover, CoverAdmin)
class PatchAdmin(admin.ModelAdmin):
@@ -104,13 +105,22 @@ class PatchAdmin(admin.ModelAdmin):
admin.site.register(Patch, PatchAdmin)
-class CommentAdmin(admin.ModelAdmin):
- list_display = ('submission', 'submitter', 'date')
- search_fields = ('submission__name', 'submitter__name', 'submitter__email')
+class CoverCommentAdmin(admin.ModelAdmin):
+ list_display = ('cover', 'submitter', 'date')
+ search_fields = ('cover__name', 'submitter__name', 'submitter__email')
date_hierarchy = 'date'
-admin.site.register(Comment, CommentAdmin)
+admin.site.register(CoverComment, CoverCommentAdmin)
+
+
+class PatchCommentAdmin(admin.ModelAdmin):
+ list_display = ('patch', 'submitter', 'date')
+ search_fields = ('patch__name', 'submitter__name', 'submitter__email')
+ date_hierarchy = 'date'
+
+
+admin.site.register(PatchComment, PatchCommentAdmin)
class PatchInline(admin.StackedInline):
diff --git a/patchwork/api/comment.py b/patchwork/api/comment.py
index 290b9cd..3802dab 100644
--- a/patchwork/api/comment.py
+++ b/patchwork/api/comment.py
@@ -12,11 +12,13 @@ from rest_framework.serializers import SerializerMethodField
from patchwork.api.base import BaseHyperlinkedModelSerializer
from patchwork.api.base import PatchworkPermission
from patchwork.api.embedded import PersonSerializer
-from patchwork.models import Comment
+from patchwork.models import Cover
+from patchwork.models import CoverComment
from patchwork.models import Submission
+from patchwork.models import PatchComment
-class CommentListSerializer(BaseHyperlinkedModelSerializer):
+class BaseCommentListSerializer(BaseHyperlinkedModelSerializer):
web_url = SerializerMethodField()
subject = SerializerMethodField()
@@ -46,7 +48,6 @@ class CommentListSerializer(BaseHyperlinkedModelSerializer):
return headers
class Meta:
- model = Comment
fields = ('id', 'web_url', 'msgid', 'list_archive_url', 'date',
'subject', 'submitter', 'content', 'headers')
read_only_fields = fields
@@ -56,11 +57,48 @@ class CommentListSerializer(BaseHyperlinkedModelSerializer):
}
-class CommentList(ListAPIView):
+class CoverCommentListSerializer(BaseCommentListSerializer):
+
+ class Meta:
+ model = CoverComment
+ fields = BaseCommentListSerializer.Meta.fields
+ read_only_fields = fields
+ versioned_fields = BaseCommentListSerializer.Meta.versioned_fields
+
+
+class PatchCommentListSerializer(BaseCommentListSerializer):
+
+ class Meta:
+ model = PatchComment
+ fields = BaseCommentListSerializer.Meta.fields
+ read_only_fields = fields
+ versioned_fields = BaseCommentListSerializer.Meta.versioned_fields
+
+
+class CoverCommentList(ListAPIView):
+ """List cover comments"""
+
+ permission_classes = (PatchworkPermission,)
+ serializer_class = CoverCommentListSerializer
+ search_fields = ('subject',)
+ ordering_fields = ('id', 'subject', 'date', 'submitter')
+ ordering = 'id'
+ lookup_url_kwarg = 'pk'
+
+ def get_queryset(self):
+ if not Cover.objects.filter(pk=self.kwargs['pk']).exists():
+ raise Http404
+
+ return CoverComment.objects.filter(
+ cover=self.kwargs['pk']
+ ).select_related('submitter')
+
+
+class PatchCommentList(ListAPIView):
"""List comments"""
permission_classes = (PatchworkPermission,)
- serializer_class = CommentListSerializer
+ serializer_class = PatchCommentListSerializer
search_fields = ('subject',)
ordering_fields = ('id', 'subject', 'date', 'submitter')
ordering = 'id'
@@ -70,6 +108,6 @@ class CommentList(ListAPIView):
if not Submission.objects.filter(pk=self.kwargs['pk']).exists():
raise Http404
- return Comment.objects.filter(
- submission=self.kwargs['pk']
+ return PatchComment.objects.filter(
+ patch=self.kwargs['pk']
).select_related('submitter')
diff --git a/patchwork/api/cover.py b/patchwork/api/cover.py
index 66d2146..b4a3a8f 100644
--- a/patchwork/api/cover.py
+++ b/patchwork/api/cover.py
@@ -15,7 +15,7 @@ from patchwork.api.filters import CoverFilterSet
from patchwork.api.embedded import PersonSerializer
from patchwork.api.embedded import ProjectSerializer
from patchwork.api.embedded import SeriesSerializer
-from patchwork.models import CoverLetter
+from patchwork.models import Cover
class CoverListSerializer(BaseHyperlinkedModelSerializer):
@@ -49,7 +49,7 @@ class CoverListSerializer(BaseHyperlinkedModelSerializer):
return data
class Meta:
- model = CoverLetter
+ model = Cover
fields = ('id', 'url', 'web_url', 'project', 'msgid',
'list_archive_url', 'date', 'name', 'submitter', 'mbox',
'series', 'comments')
@@ -82,7 +82,7 @@ class CoverDetailSerializer(CoverListSerializer):
return headers
class Meta:
- model = CoverLetter
+ model = Cover
fields = CoverListSerializer.Meta.fields + (
'headers', 'content')
read_only_fields = fields
@@ -100,7 +100,7 @@ class CoverList(ListAPIView):
ordering = 'id'
def get_queryset(self):
- return CoverLetter.objects.all()\
+ return Cover.objects.all()\
.prefetch_related('series__project')\
.select_related('project', 'submitter', 'series')\
.defer('content', 'headers')
@@ -112,5 +112,5 @@ class CoverDetail(RetrieveAPIView):
serializer_class = CoverDetailSerializer
def get_queryset(self):
- return CoverLetter.objects.all()\
+ return Cover.objects.all()\
.select_related('project', 'submitter', 'series')
diff --git a/patchwork/api/embedded.py b/patchwork/api/embedded.py
index 1478e74..3f32bd4 100644
--- a/patchwork/api/embedded.py
+++ b/patchwork/api/embedded.py
@@ -108,7 +108,7 @@ class CoverSerializer(SerializedRelatedField):
class _Serializer(MboxMixin, WebURLMixin, BaseHyperlinkedModelSerializer):
class Meta:
- model = models.CoverLetter
+ model = models.Cover
fields = ('id', 'url', 'web_url', 'msgid', 'list_archive_url',
'date', 'name', 'mbox')
read_only_fields = fields
diff --git a/patchwork/api/filters.py b/patchwork/api/filters.py
index a328d59..bd3b326 100644
--- a/patchwork/api/filters.py
+++ b/patchwork/api/filters.py
@@ -18,7 +18,7 @@ from rest_framework import exceptions
from patchwork.api import utils
from patchwork.models import Bundle
from patchwork.models import Check
-from patchwork.models import CoverLetter
+from patchwork.models import Cover
from patchwork.models import Event
from patchwork.models import Patch
from patchwork.models import Person
@@ -199,7 +199,7 @@ class CoverFilterSet(TimestampMixin, BaseFilterSet):
msgid = CharFilter(method=msgid_filter)
class Meta:
- model = CoverLetter
+ model = Cover
fields = ('project', 'series', 'submitter')
@@ -253,7 +253,7 @@ class EventFilterSet(TimestampMixin, BaseFilterSet):
patch = BaseFilter(queryset=Patch.objects.all(),
widget=MultipleHiddenInput,
distinct=False)
- cover = BaseFilter(queryset=CoverLetter.objects.all(),
+ cover = BaseFilter(queryset=Cover.objects.all(),
widget=MultipleHiddenInput,
distinct=False)
diff --git a/patchwork/management/commands/parsearchive.py b/patchwork/management/commands/parsearchive.py
index b7f1ea7..f53b8db 100644
--- a/patchwork/management/commands/parsearchive.py
+++ b/patchwork/management/commands/parsearchive.py
@@ -32,8 +32,9 @@ class Command(BaseCommand):
def handle(self, *args, **options):
results = {
models.Patch: 0,
- models.CoverLetter: 0,
- models.Comment: 0,
+ models.Cover: 0,
+ models.PatchComment: 0,
+ models.CoverComment: 0,
}
duplicates = 0
dropped = 0
@@ -118,9 +119,11 @@ class Command(BaseCommand):
' %(errors)4d errors\n'
'Total: %(new)s new entries' % {
'total': count,
- 'covers': results[models.CoverLetter],
+ 'covers': results[models.Cover],
'patches': results[models.Patch],
- 'comments': results[models.Comment],
+ 'comments': (
+ results[models.CoverComment] + results[models.PatchComment]
+ ),
'duplicates': duplicates,
'dropped': dropped,
'errors': errors,
diff --git a/patchwork/migrations/0042_add_cover_model.py b/patchwork/migrations/0042_add_cover_model.py
new file mode 100644
index 0000000..53196c1
--- /dev/null
+++ b/patchwork/migrations/0042_add_cover_model.py
@@ -0,0 +1,247 @@
+import datetime
+
+from django.db import migrations, models
+import django.db.models.deletion
+import patchwork.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('patchwork', '0041_python3'),
+ ]
+
+ operations = [
+ # create a new, separate cover (letter) model
+
+ migrations.CreateModel(
+ name='Cover',
+ fields=[
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ ('msgid', models.CharField(max_length=255)),
+ (
+ 'date',
+ models.DateTimeField(default=datetime.datetime.utcnow),
+ ),
+ ('headers', models.TextField(blank=True)),
+ ('content', models.TextField(blank=True, null=True)),
+ ('name', models.CharField(max_length=255)),
+ (
+ 'project',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='patchwork.Project',
+ ),
+ ),
+ (
+ 'submitter',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='patchwork.Person',
+ ),
+ ),
+ ],
+ options={'ordering': ['date']},
+ bases=(patchwork.models.FilenameMixin, models.Model),
+ ),
+ migrations.AddIndex(
+ model_name='cover',
+ index=models.Index(
+ fields=['date', 'project', 'submitter', 'name'],
+ name='cover_covering_idx',
+ ),
+ ),
+ migrations.AlterUniqueTogether(
+ name='cover', unique_together=set([('msgid', 'project')]),
+ ),
+
+ # create a new, separate cover (letter) comment model
+
+ migrations.CreateModel(
+ name='CoverComment',
+ fields=[
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ ('msgid', models.CharField(max_length=255)),
+ (
+ 'date',
+ models.DateTimeField(default=datetime.datetime.utcnow),
+ ),
+ ('headers', models.TextField(blank=True)),
+ ('content', models.TextField(blank=True, null=True)),
+ (
+ 'cover',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='comments',
+ related_query_name='comment',
+ to='patchwork.Cover',
+ ),
+ ),
+ (
+ 'submitter',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='patchwork.Person',
+ ),
+ ),
+ ],
+ options={'ordering': ['date']},
+ ),
+ migrations.AddIndex(
+ model_name='covercomment',
+ index=models.Index(
+ fields=['cover', 'date'], name='cover_date_idx'
+ ),
+ ),
+ migrations.AlterUniqueTogether(
+ name='covercomment', unique_together=set([('msgid', 'cover')]),
+ ),
+
+ # copy all entries from the 'CoverLetter' model to the new 'Cover'
+ # model; note that it's not possible to reverse this since we can't
+ # guarantee IDs will be unique after the split
+
+ migrations.RunSQL(
+ """
+ INSERT INTO patchwork_cover
+ (id, msgid, name, date, headers, project_id, submitter_id,
+ content)
+ SELECT s.id, s.msgid, s.name, s.date, s.headers, s.project_id,
+ s.submitter_id, s.content
+ FROM patchwork_coverletter c
+ INNER JOIN patchwork_submission s ON s.id = c.submission_ptr_id
+ """,
+ None,
+ ),
+
+ # copy all 'CoverLetter'-related comments to the new 'CoverComment'
+ # table; as above, this is non-reversible
+
+ migrations.RunSQL(
+ """
+ INSERT INTO patchwork_covercomment
+ (id, msgid, date, headers, content, cover_id, submitter_id)
+ SELECT c.id, c.msgid, c.date, c.headers, c.content,
+ c.submission_id, c.submitter_id
+ FROM patchwork_comment c
+ INNER JOIN patchwork_coverletter p
+ ON p.submission_ptr_id = c.submission_id
+ """,
+ None,
+ ),
+
+ # update all references to the 'CoverLetter' model to point to the new
+ # 'Cover' model instead
+
+ migrations.AlterField(
+ model_name='event',
+ name='cover',
+ field=models.ForeignKey(
+ blank=True,
+ help_text='The cover letter that this event was created for.',
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='+',
+ to='patchwork.Cover',
+ ),
+ ),
+ migrations.AlterField(
+ model_name='series',
+ name='cover_letter',
+ field=models.OneToOneField(
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='series',
+ to='patchwork.Cover'
+ ),
+ ),
+
+ # remove all the old 'CoverLetter'-related entries from the 'Comment'
+ # table
+
+ migrations.RunSQL(
+ """
+ DELETE patchwork_comment FROM
+ patchwork_comment
+ INNER JOIN patchwork_coverletter
+ ON patchwork_coverletter.submission_ptr_id = patchwork_comment.submission_id
+ """, # noqa
+ None
+ ),
+
+ # delete the old 'CoverLetter' model
+
+ migrations.DeleteModel(
+ name='CoverLetter',
+ ),
+
+ # rename the 'Comment.submission' field to 'Comment.patch'; note our
+ # use of 'AlterField' before and after to work around bug #31335
+ #
+ # https://code.djangoproject.com/ticket/31335
+
+ migrations.AlterField(
+ model_name='comment',
+ name='submission',
+ field=models.ForeignKey(
+ db_constraint=False,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='comments',
+ related_query_name='comment',
+ to='patchwork.Submission',
+ ),
+ ),
+ migrations.RemoveIndex(
+ model_name='comment',
+ name='submission_date_idx',
+ ),
+ migrations.RenameField(
+ model_name='comment',
+ old_name='submission',
+ new_name='patch',
+ ),
+ migrations.AlterUniqueTogether(
+ name='comment',
+ unique_together=set([('msgid', 'patch')]),
+ ),
+ migrations.AddIndex(
+ model_name='comment',
+ index=models.Index(
+ fields=['patch', 'date'],
+ name='patch_date_idx',
+ ),
+ ),
+ migrations.AlterField(
+ model_name='comment',
+ name='patch',
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='comments',
+ related_query_name='comment',
+ to='patchwork.Submission',
+ ),
+ ),
+
+ # rename the 'Comment' model to 'PatchComment'
+
+ migrations.RenameModel(
+ old_name='Comment',
+ new_name='PatchComment',
+ ),
+ ]
diff --git a/patchwork/models.py b/patchwork/models.py
index 769f602..3755b65 100644
--- a/patchwork/models.py
+++ b/patchwork/models.py
@@ -358,7 +358,7 @@ class FilenameMixin(object):
return fname
-class Submission(FilenameMixin, EmailMixin, models.Model):
+class SubmissionMixin(models.Model):
# parent
project = models.ForeignKey(Project, on_delete=models.CASCADE)
@@ -385,19 +385,10 @@ class Submission(FilenameMixin, EmailMixin, models.Model):
return self.name
class Meta:
- ordering = ['date']
- unique_together = [('msgid', 'project')]
- indexes = [
- # This is a covering index for the /list/ query
- # Like what we have for Patch, but used for displaying what we want
- # rather than for working out the count (of course, this all
- # depends on the SQL optimiser of your db engine)
- models.Index(fields=['date', 'project', 'submitter', 'name'],
- name='submission_covering_idx'),
- ]
+ abstract = True
-class CoverLetter(Submission):
+class Cover(FilenameMixin, EmailMixin, SubmissionMixin):
def get_absolute_url(self):
return reverse('cover-detail',
@@ -409,6 +400,35 @@ class CoverLetter(Submission):
kwargs={'project_id': self.project.linkname,
'msgid': self.url_msgid})
+ class Meta:
+ ordering = ['date']
+ unique_together = [('msgid', 'project')]
+ indexes = [
+ # This is a covering index for the /list/ query
+ # Like what we have for Patch, but used for displaying what we want
+ # rather than for working out the count (of course, this all
+ # depends on the SQL optimiser of your DB engine)
+ models.Index(
+ fields=['date', 'project', 'submitter', 'name'],
+ name='cover_covering_idx',
+ ),
+ ]
+
+
+class Submission(SubmissionMixin, FilenameMixin, EmailMixin):
+
+ class Meta:
+ ordering = ['date']
+ unique_together = [('msgid', 'project')]
+ indexes = [
+ # This is a covering index for the /list/ query
+ # Like what we have for Patch, but used for displaying what we want
+ # rather than for working out the count (of course, this all
+ # depends on the SQL optimiser of your db engine)
+ models.Index(fields=['date', 'project', 'submitter', 'name'],
+ name='submission_covering_idx'),
+ ]
+
class Patch(Submission):
# patch metadata
@@ -619,44 +639,79 @@ class Patch(Submission):
]
-class Comment(EmailMixin, models.Model):
+class CoverComment(EmailMixin, models.Model):
+
+ cover = models.ForeignKey(
+ Cover,
+ related_name='comments',
+ related_query_name='comment',
+ on_delete=models.CASCADE,
+ )
+
+ @property
+ def list_archive_url(self):
+ if not self.cover.project.list_archive_url_format:
+ return None
+ if not self.msgid:
+ return None
+ return self.project.list_archive_url_format.format(self.url_msgid)
+
+ def get_absolute_url(self):
+ return reverse('comment-redirect', kwargs={'comment_id': self.id})
+
+ def is_editable(self, user):
+ return False
+
+ class Meta:
+ ordering = ['date']
+ unique_together = [('msgid', 'cover')]
+ indexes = [
+ models.Index(name='cover_date_idx', fields=['cover', 'date']),
+ ]
+
+
+class PatchComment(EmailMixin, models.Model):
# parent
- submission = models.ForeignKey(Submission, related_name='comments',
- related_query_name='comment',
- on_delete=models.CASCADE)
+ patch = models.ForeignKey(
+ Submission,
+ related_name='comments',
+ related_query_name='comment',
+ on_delete=models.CASCADE,
+ )
@property
def list_archive_url(self):
- if not self.submission.project.list_archive_url_format:
+ if not self.patch.project.list_archive_url_format:
return None
if not self.msgid:
return None
- return self.project.list_archive_url_format.format(
+ return self.patch.list_archive_url_format.format(
self.url_msgid)
def get_absolute_url(self):
return reverse('comment-redirect', kwargs={'comment_id': self.id})
def save(self, *args, **kwargs):
- super(Comment, self).save(*args, **kwargs)
- if hasattr(self.submission, 'patch'):
- self.submission.patch.refresh_tag_counts()
+ super(PatchComment, self).save(*args, **kwargs)
+ # TODO(stephenfin): Update this once patch is flattened
+ if hasattr(self.patch, 'patch'):
+ self.patch.patch.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()
+ super(PatchComment, self).delete(*args, **kwargs)
+ # TODO(stephenfin): Update this once patch is flattened
+ if hasattr(self.patch, 'patch'):
+ self.patch.patch.refresh_tag_counts()
def is_editable(self, user):
return False
class Meta:
ordering = ['date']
- unique_together = [('msgid', 'submission')]
+ unique_together = [('msgid', 'patch')]
indexes = [
- models.Index(name='submission_date_idx',
- fields=['submission', 'date'])
+ models.Index(name='patch_date_idx', fields=['patch', 'date']),
]
@@ -668,9 +723,12 @@ class Series(FilenameMixin, models.Model):
blank=True, on_delete=models.CASCADE)
# content
- cover_letter = models.OneToOneField(CoverLetter, related_name='series',
- null=True,
- on_delete=models.CASCADE)
+ cover_letter = models.OneToOneField(
+ Cover,
+ related_name='series',
+ null=True,
+ on_delete=models.CASCADE
+ )
# metadata
name = models.CharField(max_length=255, blank=True, null=True,
@@ -987,7 +1045,7 @@ class Event(models.Model):
on_delete=models.CASCADE,
help_text='The series that this event was created for.')
cover = models.ForeignKey(
- CoverLetter, related_name='+', null=True, blank=True,
+ Cover, related_name='+', null=True, blank=True,
on_delete=models.CASCADE,
help_text='The cover letter that this event was created for.')
diff --git a/patchwork/parser.py b/patchwork/parser.py
index 63ff680..3e2d2ff 100644
--- a/patchwork/parser.py
+++ b/patchwork/parser.py
@@ -18,11 +18,12 @@ from django.contrib.auth.models import User
from django.db.utils import IntegrityError
from django.db import transaction
-from patchwork.models import Comment
-from patchwork.models import CoverLetter
+from patchwork.models import Cover
+from patchwork.models import CoverComment
from patchwork.models import DelegationRule
from patchwork.models import get_default_initial_patch_state
from patchwork.models import Patch
+from patchwork.models import PatchComment
from patchwork.models import Person
from patchwork.models import Project
from patchwork.models import Series
@@ -664,10 +665,12 @@ def find_submission_for_comment(project, refs):
# see if we have comments that refer to a patch
try:
- comment = Comment.objects.get(submission__project=project,
- msgid=ref)
+ comment = PatchComment.objects.get(
+ patch__project=project,
+ msgid=ref,
+ )
return comment.submission
- except Comment.MultipleObjectsReturned:
+ except PatchComment.MultipleObjectsReturned:
# NOTE(stephenfin): This is a artifact of prior lack of support
# for cover letters in Patchwork. Previously all replies to
# patches were saved as comments. However, it's possible that
@@ -681,11 +684,36 @@ def find_submission_for_comment(project, refs):
# apply the comment to the cover letter. Note that this only
# happens when running 'parsearchive' or similar, so it should not
# affect every day use in any way.
- comments = Comment.objects.filter(submission__project=project,
- msgid=ref)
+ comments = PatchComment.objects.filter(
+ patch__project=project,
+ msgid=ref,
+ )
# The latter item will be the cover letter
return comments.reverse()[0].submission
- except Comment.DoesNotExist:
+ except PatchComment.DoesNotExist:
+ pass
+
+ return None
+
+
+def find_cover_for_comment(project, refs):
+ for ref in refs:
+ ref = ref[:255]
+ # first, check for a direct reply
+ try:
+ cover = Cover.objects.get(project=project, msgid=ref)
+ return cover
+ except Cover.DoesNotExist:
+ pass
+
+ # see if we have comments that refer to a patch
+ try:
+ comment = CoverComment.objects.get(
+ cover__project=project,
+ msgid=ref,
+ )
+ return comment.cover
+ except CoverComment.DoesNotExist:
pass
return None
@@ -1190,11 +1218,11 @@ def parse_mail(mail, list_id=None):
if not is_comment:
if not refs == []:
try:
- CoverLetter.objects.all().get(name=name)
- except CoverLetter.DoesNotExist:
+ Cover.objects.all().get(name=name)
+ except Cover.DoesNotExist:
# if no match, this is a new cover letter
is_cover_letter = True
- except CoverLetter.MultipleObjectsReturned:
+ except Cover.MultipleObjectsReturned:
# if multiple cover letters are found, just ignore
pass
else:
@@ -1228,10 +1256,10 @@ def parse_mail(mail, list_id=None):
msgid=msgid, project=project, series=series)
with transaction.atomic():
- if CoverLetter.objects.filter(project=project, msgid=msgid):
+ if Cover.objects.filter(project=project, msgid=msgid):
raise DuplicateMailError(msgid=msgid)
- cover_letter = CoverLetter.objects.create(
+ cover_letter = Cover.objects.create(
msgid=msgid,
project=project,
name=name[:255],
@@ -1250,16 +1278,35 @@ def parse_mail(mail, list_id=None):
# we only save comments if we have the parent email
submission = find_submission_for_comment(project, refs)
- if not submission:
+ if submission:
+ author = get_or_create_author(mail, project)
+
+ with transaction.atomic():
+ if PatchComment.objects.filter(patch=patch, msgid=msgid):
+ raise DuplicateMailError(msgid=msgid)
+ comment = PatchComment.objects.create(
+ patch=submission,
+ msgid=msgid,
+ date=date,
+ headers=headers,
+ submitter=author,
+ content=message)
+
+ logger.debug('Comment saved')
+
+ return comment
+
+ cover = find_cover_for_comment(project, refs)
+ if not cover:
return
author = get_or_create_author(mail, project)
with transaction.atomic():
- if Comment.objects.filter(submission=submission, msgid=msgid):
+ if CoverComment.objects.filter(cover=cover, msgid=msgid):
raise DuplicateMailError(msgid=msgid)
- comment = Comment.objects.create(
- submission=submission,
+ comment = CoverComment.objects.create(
+ cover=cover,
msgid=msgid,
date=date,
headers=headers,
diff --git a/patchwork/signals.py b/patchwork/signals.py
index 3a2f0fb..4db9909 100644
--- a/patchwork/signals.py
+++ b/patchwork/signals.py
@@ -10,7 +10,7 @@ from django.db.models.signals import pre_save
from django.dispatch import receiver
from patchwork.models import Check
-from patchwork.models import CoverLetter
+from patchwork.models import Cover
from patchwork.models import Event
from patchwork.models import Patch
from patchwork.models import PatchChangeNotification
@@ -54,7 +54,7 @@ def patch_change_callback(sender, instance, raw, **kwargs):
notification.save()
-@receiver(post_save, sender=CoverLetter)
+@receiver(post_save, sender=Cover)
def create_cover_created_event(sender, instance, created, raw, **kwargs):
def create_event(cover):
diff --git a/patchwork/tests/api/test_comment.py b/patchwork/tests/api/test_comment.py
index f48bfce..dfbf904 100644
--- a/patchwork/tests/api/test_comment.py
+++ b/patchwork/tests/api/test_comment.py
@@ -10,9 +10,10 @@ from django.urls import NoReverseMatch
from django.urls import reverse
from patchwork.tests.api import utils
-from patchwork.tests.utils import create_comment
from patchwork.tests.utils import create_cover
+from patchwork.tests.utils import create_cover_comment
from patchwork.tests.utils import create_patch
+from patchwork.tests.utils import create_patch_comment
from patchwork.tests.utils import SAMPLE_CONTENT
if settings.ENABLE_REST_API:
@@ -47,7 +48,7 @@ class TestCoverComments(utils.APITestCase):
def test_list(self):
"""List cover letter comments."""
cover = create_cover()
- comment = create_comment(submission=cover)
+ comment = create_cover_comment(cover=cover)
resp = self.client.get(self.api_url(cover))
self.assertEqual(status.HTTP_200_OK, resp.status_code)
@@ -57,7 +58,7 @@ class TestCoverComments(utils.APITestCase):
def test_list_version_1_0(self):
"""List cover letter comments using API v1.0."""
cover = create_cover()
- create_comment(submission=cover)
+ create_cover_comment(cover=cover)
# check we can't access comments using the old version of the API
with self.assertRaises(NoReverseMatch):
@@ -98,7 +99,7 @@ class TestPatchComments(utils.APITestCase):
def test_list(self):
"""List patch comments."""
patch = create_patch()
- comment = create_comment(submission=patch)
+ comment = create_patch_comment(patch=patch)
resp = self.client.get(self.api_url(patch))
self.assertEqual(status.HTTP_200_OK, resp.status_code)
@@ -108,7 +109,7 @@ class TestPatchComments(utils.APITestCase):
def test_list_version_1_0(self):
"""List patch comments using API v1.0."""
patch = create_patch()
- create_comment(submission=patch)
+ create_patch_comment(patch=patch)
# check we can't access comments using the old version of the API
with self.assertRaises(NoReverseMatch):
diff --git a/patchwork/tests/test_detail.py b/patchwork/tests/test_detail.py
index ddc2b93..393ebc7 100644
--- a/patchwork/tests/test_detail.py
+++ b/patchwork/tests/test_detail.py
@@ -6,9 +6,10 @@
from django.test import TestCase
from django.urls import reverse
-from patchwork.tests.utils import create_comment
from patchwork.tests.utils import create_cover
+from patchwork.tests.utils import create_cover_comment
from patchwork.tests.utils import create_patch
+from patchwork.tests.utils import create_patch_comment
from patchwork.tests.utils import create_project
@@ -159,24 +160,32 @@ class PatchViewTest(TestCase):
class CommentRedirectTest(TestCase):
- def _test_redirect(self, submission, submission_url):
- comment_id = create_comment(submission=submission).id
+ def test_patch_redirect(self):
+ patch = create_patch()
+ comment_id = create_patch_comment(patch=patch).id
requested_url = reverse('comment-redirect',
kwargs={'comment_id': comment_id})
redirect_url = '%s#%d' % (
- reverse(submission_url,
- kwargs={'project_id': submission.project.linkname,
- 'msgid': submission.url_msgid}),
+ reverse('patch-detail',
+ kwargs={'project_id': patch.project.linkname,
+ 'msgid': patch.url_msgid}),
comment_id)
response = self.client.get(requested_url)
self.assertRedirects(response, redirect_url)
- def test_patch_redirect(self):
- patch = create_patch()
- self._test_redirect(patch, 'patch-detail')
-
def test_cover_redirect(self):
cover = create_cover()
- self._test_redirect(cover, 'cover-detail')
+ comment_id = create_cover_comment(cover=cover).id
+
+ requested_url = reverse('comment-redirect',
+ kwargs={'comment_id': comment_id})
+ redirect_url = '%s#%d' % (
+ reverse('cover-detail',
+ kwargs={'project_id': cover.project.linkname,
+ 'msgid': cover.url_msgid}),
+ comment_id)
+
+ response = self.client.get(requested_url)
+ self.assertRedirects(response, redirect_url)
diff --git a/patchwork/tests/test_mboxviews.py b/patchwork/tests/test_mboxviews.py
index 1e7bfb0..a7b0186 100644
--- a/patchwork/tests/test_mboxviews.py
+++ b/patchwork/tests/test_mboxviews.py
@@ -13,8 +13,8 @@ import email
from django.test import TestCase
from django.urls import reverse
-from patchwork.tests.utils import create_comment
from patchwork.tests.utils import create_patch
+from patchwork.tests.utils import create_patch_comment
from patchwork.tests.utils import create_project
from patchwork.tests.utils import create_person
from patchwork.tests.utils import create_series
@@ -34,8 +34,8 @@ class MboxPatchResponseTest(TestCase):
project=self.project,
submitter=self.person,
content='comment 1 text\nAcked-by: 1\n')
- create_comment(
- submission=patch,
+ create_patch_comment(
+ patch=patch,
submitter=self.person,
content='comment 2 text\nAcked-by: 2\n')
response = self.client.get(
@@ -48,8 +48,8 @@ class MboxPatchResponseTest(TestCase):
project=self.project,
submitter=self.person,
content='patch text\n')
- create_comment(
- submission=patch,
+ create_patch_comment(
+ patch=patch,
submitter=self.person,
content=u'comment\nAcked-by:\u00A0 foo')
response = self.client.get(
@@ -71,8 +71,8 @@ class MboxPatchSplitResponseTest(TestCase):
submitter=self.person,
diff='',
content='comment 1 text\nAcked-by: 1\n---\nupdate\n')
- self.comment = create_comment(
- submission=self.patch,
+ self.comment = create_patch_comment(
+ patch=self.patch,
submitter=self.person,
content='comment 2 text\nAcked-by: 2\n')
diff --git a/patchwork/tests/test_parser.py b/patchwork/tests/test_parser.py
index 07c2b97..99e27f1 100644
--- a/patchwork/tests/test_parser.py
+++ b/patchwork/tests/test_parser.py
@@ -17,11 +17,11 @@ from django.test import TransactionTestCase
from django.db.transaction import atomic
from django.db import connection
-from patchwork.models import Comment
+from patchwork.models import Cover
from patchwork.models import Patch
+from patchwork.models import PatchComment
from patchwork.models import Person
from patchwork.models import State
-from patchwork.models import CoverLetter
from patchwork.parser import clean_subject
from patchwork.parser import get_or_create_author
from patchwork.parser import find_patch_content as find_content
@@ -551,7 +551,7 @@ class MultipleProjectPatchCommentTest(MultipleProjectPatchTest):
patch = Patch.objects.filter(project=project)[0]
# we should see the reply comment only
self.assertEqual(
- Comment.objects.filter(submission=patch).count(), 1)
+ PatchComment.objects.filter(patch=patch).count(), 1)
class ListIdHeaderTest(TestCase):
@@ -1157,7 +1157,7 @@ class DuplicateMailTest(TestCase):
self._test_duplicate_mail(m2)
self.assertEqual(Patch.objects.count(), 1)
- self.assertEqual(Comment.objects.count(), 1)
+ self.assertEqual(PatchComment.objects.count(), 1)
def test_duplicate_coverletter(self):
m = create_email('test', listid=self.listid, msgid='1@example.com')
@@ -1166,4 +1166,4 @@ class DuplicateMailTest(TestCase):
self._test_duplicate_mail(m)
- self.assertEqual(CoverLetter.objects.count(), 1)
+ self.assertEqual(Cover.objects.count(), 1)
diff --git a/patchwork/tests/test_series.py b/patchwork/tests/test_series.py
index 2e86e47..e68ee88 100644
--- a/patchwork/tests/test_series.py
+++ b/patchwork/tests/test_series.py
@@ -35,7 +35,7 @@ class _BaseTestCase(TestCase):
mbox = mailbox.mbox(os.path.join(TEST_SERIES_DIR, name), create=False)
for msg in mbox:
obj = parser.parse_mail(msg, project.listid)
- if type(obj) == models.CoverLetter:
+ if type(obj) == models.Cover:
results[0].append(obj)
elif type(obj) == models.Patch:
results[1].append(obj)
diff --git a/patchwork/tests/test_tags.py b/patchwork/tests/test_tags.py
index 97afba0..6c13687 100644
--- a/patchwork/tests/test_tags.py
+++ b/patchwork/tests/test_tags.py
@@ -9,8 +9,8 @@ from django.test import TransactionTestCase
from patchwork.models import Patch
from patchwork.models import PatchTag
from patchwork.models import Tag
-from patchwork.tests.utils import create_comment
from patchwork.tests.utils import create_patch
+from patchwork.tests.utils import create_patch_comment
class ExtractTagsTest(TestCase):
@@ -107,8 +107,8 @@ class PatchTagsTest(TransactionTestCase):
return '%s-by: Test Tagger <tagger@example.com>\n' % tags[tagtype]
def create_tag_comment(self, patch, tagtype=None):
- comment = create_comment(
- submission=patch,
+ comment = create_patch_comment(
+ patch=patch,
content=self.create_tag(tagtype))
return comment
diff --git a/patchwork/tests/utils.py b/patchwork/tests/utils.py
index 1a7baa2..83bd66a 100644
--- a/patchwork/tests/utils.py
+++ b/patchwork/tests/utils.py
@@ -13,9 +13,10 @@ from django.contrib.auth.models import User
from patchwork.models import Bundle
from patchwork.models import Check
-from patchwork.models import Comment
-from patchwork.models import CoverLetter
+from patchwork.models import Cover
+from patchwork.models import CoverComment
from patchwork.models import Patch
+from patchwork.models import PatchComment
from patchwork.models import PatchRelation
from patchwork.models import Person
from patchwork.models import Project
@@ -203,8 +204,8 @@ def create_patch(**kwargs):
def create_cover(**kwargs):
- """Create 'CoverLetter' object."""
- num = CoverLetter.objects.count()
+ """Create 'Cover' object."""
+ num = Cover.objects.count()
# NOTE(stephenfin): Despite first appearances, passing 'series' to the
# 'create' function doesn't actually cause the relationship to be created.
@@ -232,7 +233,7 @@ def create_cover(**kwargs):
}
values.update(kwargs)
- cover = CoverLetter.objects.create(**values)
+ cover = Cover.objects.create(**values)
if series:
series.add_cover_letter(cover)
@@ -240,17 +241,30 @@ def create_cover(**kwargs):
return cover
-def create_comment(**kwargs):
- """Create 'Comment' object."""
+def create_cover_comment(**kwargs):
+ """Create 'CoverComment' object."""
values = {
'submitter': create_person() if 'submitter' not in kwargs else None,
- 'submission': create_patch() if 'submission' not in kwargs else None,
+ 'cover': create_cover() if 'cover' not in kwargs else None,
'msgid': make_msgid(),
'content': SAMPLE_CONTENT,
}
values.update(kwargs)
- return Comment.objects.create(**values)
+ return CoverComment.objects.create(**values)
+
+
+def create_patch_comment(**kwargs):
+ """Create 'PatchComment' object."""
+ values = {
+ 'submitter': create_person() if 'submitter' not in kwargs else None,
+ 'patch': create_patch() if 'patch' not in kwargs else None,
+ 'msgid': make_msgid(),
+ 'content': SAMPLE_CONTENT,
+ }
+ values.update(kwargs)
+
+ return PatchComment.objects.create(**values)
def create_check(**kwargs):
diff --git a/patchwork/urls.py b/patchwork/urls.py
index 9c3b85f..7d888d4 100644
--- a/patchwork/urls.py
+++ b/patchwork/urls.py
@@ -249,10 +249,10 @@ if settings.ENABLE_REST_API:
api_1_1_patterns = [
url(r'^patches/(?P<pk>[^/]+)/comments/$',
- api_comment_views.CommentList.as_view(),
+ api_comment_views.PatchCommentList.as_view(),
name='api-patch-comment-list'),
url(r'^covers/(?P<pk>[^/]+)/comments/$',
- api_comment_views.CommentList.as_view(),
+ api_comment_views.CoverCommentList.as_view(),
name='api-cover-comment-list'),
]
diff --git a/patchwork/views/comment.py b/patchwork/views/comment.py
index 7dee8dc..4f69922 100644
--- a/patchwork/views/comment.py
+++ b/patchwork/views/comment.py
@@ -4,20 +4,41 @@
# SPDX-License-Identifier: GPL-2.0-or-later
from django import http
-from django import shortcuts
from django.urls import reverse
from patchwork import models
def comment(request, comment_id):
- submission = shortcuts.get_object_or_404(models.Comment,
- id=comment_id).submission
- if models.Patch.objects.filter(id=submission.id).exists():
- url = 'patch-detail'
- else:
- url = 'cover-detail'
-
- return http.HttpResponseRedirect('%s#%s' % (
- reverse(url, kwargs={'project_id': submission.project.linkname,
- 'msgid': submission.url_msgid}), comment_id))
+ patch = None
+ cover = None
+
+ try:
+ patch = models.PatchComment.objects.get(id=comment_id).patch
+ except models.PatchComment.DoesNotExist:
+ try:
+ cover = models.CoverComment.objects.get(id=comment_id).cover
+ except models.CoverComment.DoesNotExist:
+ pass
+
+ if not patch and not cover:
+ raise http.Http404('No comment matches the given query.')
+
+ if patch:
+ url = reverse(
+ 'patch-detail',
+ kwargs={
+ 'project_id': patch.project.linkname,
+ 'msgid': patch.url_msgid,
+ },
+ )
+ else: # cover
+ url = reverse(
+ 'cover-detail',
+ kwargs={
+ 'project_id': cover.project.linkname,
+ 'msgid': cover.url_msgid,
+ },
+ )
+
+ return http.HttpResponseRedirect('%s#%s' % (url, comment_id))
diff --git a/patchwork/views/cover.py b/patchwork/views/cover.py
index e90b737..8ab0ba9 100644
--- a/patchwork/views/cover.py
+++ b/patchwork/views/cover.py
@@ -10,7 +10,7 @@ from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.urls import reverse
-from patchwork.models import CoverLetter
+from patchwork.models import Cover
from patchwork.models import Patch
from patchwork.models import Project
from patchwork.views.utils import cover_to_mbox
@@ -22,7 +22,7 @@ def cover_detail(request, project_id, msgid):
# redirect to patches where necessary
try:
- cover = get_object_or_404(CoverLetter, project_id=project.id,
+ cover = get_object_or_404(Cover, project_id=project.id,
msgid=db_msgid)
except Http404 as exc:
patches = Patch.objects.filter(
@@ -44,7 +44,7 @@ def cover_detail(request, project_id, msgid):
comments = cover.comments.all()
comments = comments.select_related('submitter')
comments = comments.only('submitter', 'date', 'id', 'content',
- 'submission')
+ 'cover')
context['comments'] = comments
return render(request, 'patchwork/submission.html', context)
@@ -53,7 +53,7 @@ def cover_detail(request, project_id, msgid):
def cover_mbox(request, project_id, msgid):
db_msgid = ('<%s>' % msgid)
project = get_object_or_404(Project, linkname=project_id)
- cover = get_object_or_404(CoverLetter, project_id=project.id,
+ cover = get_object_or_404(Cover, project_id=project.id,
msgid=db_msgid)
response = HttpResponse(content_type='text/plain')
@@ -65,7 +65,7 @@ def cover_mbox(request, project_id, msgid):
def cover_by_id(request, cover_id):
- cover = get_object_or_404(CoverLetter, id=cover_id)
+ cover = get_object_or_404(Cover, id=cover_id)
url = reverse('cover-detail', kwargs={'project_id': cover.project.linkname,
'msgid': cover.url_msgid})
@@ -74,7 +74,7 @@ def cover_by_id(request, cover_id):
def cover_mbox_by_id(request, cover_id):
- cover = get_object_or_404(CoverLetter, id=cover_id)
+ cover = get_object_or_404(Cover, id=cover_id)
url = reverse('cover-mbox', kwargs={'project_id': cover.project.linkname,
'msgid': cover.url_msgid})
diff --git a/patchwork/views/patch.py b/patchwork/views/patch.py
index 9fdbbf9..5d772fd 100644
--- a/patchwork/views/patch.py
+++ b/patchwork/views/patch.py
@@ -15,7 +15,7 @@ from django.urls import reverse
from patchwork.forms import CreateBundleForm
from patchwork.forms import PatchForm
from patchwork.models import Bundle
-from patchwork.models import CoverLetter
+from patchwork.models import Cover
from patchwork.models import Patch
from patchwork.models import Project
from patchwork.views import generic_list
@@ -42,7 +42,7 @@ def patch_detail(request, project_id, msgid):
try:
patch = Patch.objects.get(project_id=project.id, msgid=db_msgid)
except Patch.DoesNotExist:
- covers = CoverLetter.objects.filter(
+ covers = Cover.objects.filter(
project_id=project.id,
msgid=db_msgid,
)
@@ -109,8 +109,7 @@ def patch_detail(request, project_id, msgid):
comments = patch.comments.all()
comments = comments.select_related('submitter')
- comments = comments.only('submitter', 'date', 'id', 'content',
- 'submission')
+ comments = comments.only('submitter', 'date', 'id', 'content', 'patch')
if patch.related:
related_same_project = patch.related.patches.only(
diff --git a/patchwork/views/utils.py b/patchwork/views/utils.py
index 4419702..f02948c 100644
--- a/patchwork/views/utils.py
+++ b/patchwork/views/utils.py
@@ -15,8 +15,9 @@ import re
from django.conf import settings
from django.http import Http404
-from patchwork.models import Comment
+from patchwork.models import CoverComment
from patchwork.models import Patch
+from patchwork.models import PatchComment
from patchwork.parser import split_from_header
if settings.ENABLE_REST_API:
@@ -34,12 +35,12 @@ class PatchMbox(MIMENonMultipart):
def _submission_to_mbox(submission):
- """Get an mbox representation of a single Submission.
+ """Get an mbox representation of a single submission.
- Handles both Patch and CoverLetter objects.
+ Handles both Patch and Cover objects.
Arguments:
- submission: The Patch object to convert.
+ submission: The Patch or Cover object to convert.
Returns:
A string for the mbox file.
@@ -61,8 +62,12 @@ def _submission_to_mbox(submission):
postscript = ''
# TODO(stephenfin): Make this use the tags infrastructure
- for comment in Comment.objects.filter(submission=submission):
- body += comment.patch_responses
+ if is_patch:
+ for comment in PatchComment.objects.filter(patch=submission):
+ body += comment.patch_responses
+ else:
+ for comment in CoverComment.objects.filter(cover=submission):
+ body += comment.patch_responses
if postscript:
body += '---\n' + postscript + '\n'