[PATCH 5/5] models: Merge 'Patch' and 'Submission'

Daniel Axtens dja at axtens.net
Mon Mar 16 13:52:44 AEDT 2020


Stephen Finucane <stephen at that.guru> writes:

> Oh, the follies of youth. Time to undo the damage of 2.0.0, specifically
> commit 86172ccc16, which split Patch into two separate models using
> concrete inheritance. As noted previously, this introduced a large
> number of unavoidable JOINs across large tables and the performance
> impacts these introduce are blocking other features we want, such as
> improved tagging functionality. To combine these two models, we must do
> the following:
>
> - Update any references to the 'Patch' model to point to the
>   'Submission' model instead
> - Move everything from 'Patch' to 'Submission', including both fields
>   and options
> - Delete the 'Patch' model
> - Rename the 'Submission' model to 'Patch'
>
> With this change, our model "hierarchy" goes from:
>
>   Submission
>     Patch
>     PatchComment
>   Cover
>     CoverComment
>
> To a nice, flat:
>
>   Patch
>     PatchComment
>   Cover
>     CoverComment
>
> I expect this migration to be intensive, particularly for MySQL users
> who will see their entire tables rewritten. Unfortunately I don't see
> any way to resolve this in an easier manner.
>
> Also note that we have to use two migrations and separate the moving of
> fields from the renaming of models, in order to work around a bug in
> Django 1.11. This bug has been fixed since Django 2.0 [1][2] but the fix
> wasn't backported and Django 1.11 is only accepting security fixes at
> this point in time.
>
> [1] https://code.djangoproject.com/ticket/25530
> [2] https://code.djangoproject.com/ticket/31337
>
> Signed-off-by: Stephen Finucane <stephen at that.guru>
> Cc: Daniel Axtens <dja at axtens.net>
> Cc: Stewart Smith <stewart at linux.ibm.com>
> ---
> TODO:
> - Figure out if the indexes are correct (Stewart?). I've just included
>   everything that's displayed on the '/list' page.

You might have more luck with stewart at flamingspork.com now that Stewart
has ascended to the cloud.

Regards,
Daniel

> - If this isn't merged until 3.0, combine the migrations since we will
>   no longer have to support Django 1.11.
> ---
>  patchwork/api/comment.py                      |   4 +-
>  patchwork/management/commands/dumparchive.py  |   2 +-
>  .../0041_merge_patch_submission_a.py          | 281 ++++++++++++++++++
>  .../0042_merge_patch_submission_b.py          |  17 ++
>  patchwork/models.py                           |  94 +++---
>  patchwork/parser.py                           |  16 +-
>  patchwork/tests/test_mboxviews.py             |  26 +-
>  patchwork/tests/utils.py                      |   5 +-
>  patchwork/views/__init__.py                   |   2 +-
>  patchwork/views/utils.py                      |   4 +-
>  10 files changed, 376 insertions(+), 75 deletions(-)
>  create mode 100644 patchwork/migrations/0041_merge_patch_submission_a.py
>  create mode 100644 patchwork/migrations/0042_merge_patch_submission_b.py
>
> diff --git a/patchwork/api/comment.py b/patchwork/api/comment.py
> index 3802dab9..43b26c61 100644
> --- a/patchwork/api/comment.py
> +++ b/patchwork/api/comment.py
> @@ -14,7 +14,7 @@ from patchwork.api.base import PatchworkPermission
>  from patchwork.api.embedded import PersonSerializer
>  from patchwork.models import Cover
>  from patchwork.models import CoverComment
> -from patchwork.models import Submission
> +from patchwork.models import Patch
>  from patchwork.models import PatchComment
>  
>  
> @@ -105,7 +105,7 @@ class PatchCommentList(ListAPIView):
>      lookup_url_kwarg = 'pk'
>  
>      def get_queryset(self):
> -        if not Submission.objects.filter(pk=self.kwargs['pk']).exists():
> +        if not Patch.objects.filter(pk=self.kwargs['pk']).exists():
>              raise Http404
>  
>          return PatchComment.objects.filter(
> diff --git a/patchwork/management/commands/dumparchive.py b/patchwork/management/commands/dumparchive.py
> index 9ee80c8b..e9445eab 100644
> --- a/patchwork/management/commands/dumparchive.py
> +++ b/patchwork/management/commands/dumparchive.py
> @@ -58,7 +58,7 @@ class Command(BaseCommand):
>                      i + 1, len(projects), project.linkname))
>  
>                  with tempfile.NamedTemporaryFile(delete=False) as mbox:
> -                    patches = Patch.objects.filter(patch_project=project)
> +                    patches = Patch.objects.filter(project=project)
>                      count = patches.count()
>                      for j, patch in enumerate(patches):
>                          if not (j % 10):
> diff --git a/patchwork/migrations/0041_merge_patch_submission_a.py b/patchwork/migrations/0041_merge_patch_submission_a.py
> new file mode 100644
> index 00000000..80b8dc2f
> --- /dev/null
> +++ b/patchwork/migrations/0041_merge_patch_submission_a.py
> @@ -0,0 +1,281 @@
> +# -*- coding: utf-8 -*-
> +from __future__ import unicode_literals
> +
> +from django.conf import settings
> +from django.db import connection, migrations, models
> +import django.db.models.deletion
> +
> +import patchwork.fields
> +
> +
> +def migrate_data(apps, schema_editor):
> +    if connection.vendor == 'postgresql':
> +        schema_editor.execute(
> +            """
> +            UPDATE patchwork_submission
> +              SET archived = patchwork_patch.archived2,
> +                  commit_ref = patchwork_patch.commit_ref2,
> +                  delegate_id = patchwork_patch.delegate2_id,
> +                  diff = patchwork_patch.diff2,
> +                  hash = patchwork_patch.hash2,
> +                  number = patchwork_patch.number2,
> +                  pull_url = patchwork_patch.pull_url2,
> +                  series_id = patchwork_patch.series2_id,
> +                  state_id = patchwork_patch.state2_id,
> +            FROM patchwork_patch
> +              WHERE patchwork_submission.id = patchwork_patch.submission_ptr_id
> +            """
> +        )
> +    elif connection.vendor == 'mysql':
> +        schema_editor.execute(
> +            """
> +            UPDATE patchwork_submission, patchwork_patch
> +              SET patchwork_submission.archived = patchwork_patch.archived2,
> +                  patchwork_submission.commit_ref = patchwork_patch.commit_ref2,
> +                  patchwork_submission.delegate_id = patchwork_patch.delegate2_id,
> +                  patchwork_submission.diff = patchwork_patch.diff2,
> +                  patchwork_submission.hash = patchwork_patch.hash2,
> +                  patchwork_submission.number = patchwork_patch.number2,
> +                  patchwork_submission.pull_url = patchwork_patch.pull_url2,
> +                  patchwork_submission.series_id = patchwork_patch.series2_id,
> +                  patchwork_submission.state_id = patchwork_patch.state2_id
> +            WHERE patchwork_submission.id = patchwork_patch.submission_ptr_id
> +            """  # noqa
> +        )
> +    else:
> +        raise Exception('DB not supported')
> +
> +
> +class Migration(migrations.Migration):
> +
> +    dependencies = [
> +        ('patchwork', '0040_add_cover_model'),
> +    ]
> +
> +    operations = [
> +        # move the 'PatchTag' model to point to 'Submission'
> +
> +        migrations.RemoveField(model_name='patch', name='tags',),
> +        migrations.AddField(
> +            model_name='submission',
> +            name='tags',
> +            field=models.ManyToManyField(
> +                through='patchwork.PatchTag', to='patchwork.Tag'
> +            ),
> +        ),
> +        migrations.AlterField(
> +            model_name='patchtag',
> +            name='patch',
> +            field=models.ForeignKey(
> +                on_delete=django.db.models.deletion.CASCADE,
> +                to='patchwork.Submission',
> +            ),
> +        ),
> +
> +        # do the same for any other field that references 'Patch'
> +
> +        migrations.AlterField(
> +            model_name='bundle',
> +            name='patches',
> +            field=models.ManyToManyField(
> +                through='patchwork.BundlePatch', to='patchwork.Submission'
> +            ),
> +        ),
> +        migrations.AlterField(
> +            model_name='bundlepatch',
> +            name='patch',
> +            field=models.ForeignKey(
> +                on_delete=django.db.models.deletion.CASCADE,
> +                to='patchwork.Submission',
> +            ),
> +        ),
> +        migrations.AlterField(
> +            model_name='check',
> +            name='patch',
> +            field=models.ForeignKey(
> +                on_delete=django.db.models.deletion.CASCADE,
> +                to='patchwork.Submission',
> +            ),
> +        ),
> +        migrations.AlterField(
> +            model_name='event',
> +            name='patch',
> +            field=models.ForeignKey(
> +                blank=True,
> +                help_text=b'The patch that this event was created for.',
> +                null=True,
> +                on_delete=django.db.models.deletion.CASCADE,
> +                related_name='+',
> +                to='patchwork.Submission',
> +            ),
> +        ),
> +        migrations.AlterField(
> +            model_name='patchchangenotification',
> +            name='patch',
> +            field=models.OneToOneField(
> +                on_delete=django.db.models.deletion.CASCADE,
> +                primary_key=True,
> +                serialize=False,
> +                to='patchwork.Submission',
> +            ),
> +        ),
> +
> +        # rename all the fields on 'Patch' so we don't have duplicates when we
> +        # add them to 'Submission'
> +
> +        migrations.RemoveIndex(
> +            model_name='patch', name='patch_list_covering_idx',
> +        ),
> +        migrations.AlterUniqueTogether(name='patch', unique_together=set([]),),
> +        migrations.RenameField(
> +            model_name='patch', old_name='archived', new_name='archived2',
> +        ),
> +        migrations.RenameField(
> +            model_name='patch', old_name='commit_ref', new_name='commit_ref2',
> +        ),
> +        migrations.RenameField(
> +            model_name='patch', old_name='delegate', new_name='delegate2',
> +        ),
> +        migrations.RenameField(
> +            model_name='patch', old_name='diff', new_name='diff2',
> +        ),
> +        migrations.RenameField(
> +            model_name='patch', old_name='hash', new_name='hash2',
> +        ),
> +        migrations.RenameField(
> +            model_name='patch', old_name='number', new_name='number2',
> +        ),
> +        migrations.RenameField(
> +            model_name='patch', old_name='pull_url', new_name='pull_url2',
> +        ),
> +        migrations.RenameField(
> +            model_name='patch', old_name='series', new_name='series2',
> +        ),
> +        migrations.RenameField(
> +            model_name='patch', old_name='state', new_name='state2',
> +        ),
> +
> +        # add the fields found on 'Patch' to 'Submission'
> +
> +        migrations.AddField(
> +            model_name='submission',
> +            name='archived',
> +            field=models.BooleanField(default=False),
> +        ),
> +        migrations.AddField(
> +            model_name='submission',
> +            name='commit_ref',
> +            field=models.CharField(blank=True, max_length=255, null=True),
> +        ),
> +        migrations.AddField(
> +            model_name='submission',
> +            name='delegate',
> +            field=models.ForeignKey(
> +                blank=True,
> +                null=True,
> +                on_delete=django.db.models.deletion.CASCADE,
> +                to=settings.AUTH_USER_MODEL,
> +            ),
> +        ),
> +        migrations.AddField(
> +            model_name='submission',
> +            name='diff',
> +            field=models.TextField(blank=True, null=True),
> +        ),
> +        migrations.AddField(
> +            model_name='submission',
> +            name='hash',
> +            field=patchwork.fields.HashField(
> +                blank=True, max_length=40, null=True
> +            ),
> +        ),
> +        migrations.AddField(
> +            model_name='submission',
> +            name='number',
> +            field=models.PositiveSmallIntegerField(
> +                default=None,
> +                help_text=b'The number assigned to this patch in the series',
> +                null=True,
> +            ),
> +        ),
> +        migrations.AddField(
> +            model_name='submission',
> +            name='pull_url',
> +            field=models.CharField(blank=True, max_length=255, null=True),
> +        ),
> +        migrations.AddField(
> +            model_name='submission',
> +            name='series',
> +            field=models.ForeignKey(
> +                blank=True,
> +                null=True,
> +                on_delete=django.db.models.deletion.CASCADE,
> +                related_name='patches',
> +                related_query_name='patch',
> +                to='patchwork.Series',
> +            ),
> +        ),
> +        migrations.AddField(
> +            model_name='submission',
> +            name='state',
> +            field=models.ForeignKey(
> +                null=True,
> +                on_delete=django.db.models.deletion.CASCADE,
> +                to='patchwork.State',
> +            ),
> +        ),
> +
> +        # copy the data from 'Patch' to 'Submission'
> +
> +        migrations.RunPython(migrate_data, None, atomic=False),
> +
> +        # configure metadata for the 'Submission' model
> +
> +        migrations.AlterModelOptions(
> +            name='submission',
> +            options={
> +                'base_manager_name': 'objects',
> +                'ordering': ['date'],
> +                'verbose_name_plural': 'Patches',
> +            },
> +        ),
> +        migrations.AlterUniqueTogether(
> +            name='submission',
> +            unique_together=set([('series', 'number'), ('msgid', 'project')]),
> +        ),
> +        migrations.RemoveIndex(
> +            model_name='submission', name='submission_covering_idx',
> +        ),
> +        migrations.AddIndex(
> +            model_name='submission',
> +            index=models.Index(
> +                fields=[
> +                    b'archived',
> +                    b'state',
> +                    b'delegate',
> +                    b'date',
> +                    b'project',
> +                    b'submitter',
> +                    b'name',
> +                ],
> +                name=b'patch_covering_idx',
> +            ),
> +        ),
> +
> +        # remove the foreign key fields from the 'Patch' model
> +
> +        migrations.RemoveField(model_name='patch', name='delegate2',),
> +        migrations.RemoveField(model_name='patch', name='patch_project',),
> +        migrations.RemoveField(model_name='patch', name='series2',),
> +        migrations.RemoveField(model_name='patch', name='state2',),
> +        migrations.RemoveField(model_name='patch', name='submission_ptr',),
> +
> +        # drop the 'Patch' model and rename 'Submission' to 'Patch'; this is
> +        # done in a seperate migration to work around bug #31337 for anyone
> +        # still using Django 1.11
> +        #
> +        # https://code.djangoproject.com/ticket/3133
> +
> +        # migrations.DeleteModel(name='Patch',),
> +        # migrations.RenameModel(old_name='Submission', new_name='Patch',),
> +    ]
> diff --git a/patchwork/migrations/0042_merge_patch_submission_b.py b/patchwork/migrations/0042_merge_patch_submission_b.py
> new file mode 100644
> index 00000000..0051ce87
> --- /dev/null
> +++ b/patchwork/migrations/0042_merge_patch_submission_b.py
> @@ -0,0 +1,17 @@
> +# -*- coding: utf-8 -*-
> +
> +from __future__ import unicode_literals
> +
> +from django.db import migrations
> +
> +
> +class Migration(migrations.Migration):
> +
> +    dependencies = [
> +        ('patchwork', '0041_merge_patch_submission_a'),
> +    ]
> +
> +    operations = [
> +        migrations.DeleteModel(name='Patch',),
> +        migrations.RenameModel(old_name='Submission', new_name='Patch',),
> +    ]
> diff --git a/patchwork/models.py b/patchwork/models.py
> index 37cbce32..c7b95de4 100644
> --- a/patchwork/models.py
> +++ b/patchwork/models.py
> @@ -169,7 +169,7 @@ class UserProfile(models.Model):
>      @property
>      def contributor_projects(self):
>          submitters = Person.objects.filter(user=self.user)
> -        return Project.objects.filter(id__in=Submission.objects.filter(
> +        return Project.objects.filter(id__in=Patch.objects.filter(
>              submitter__in=submitters).values('project_id').query)
>  
>      @property
> @@ -292,8 +292,7 @@ class PatchQuerySet(models.query.QuerySet):
>              select[tag.attr_name] = (
>                  "coalesce("
>                  "(SELECT count FROM patchwork_patchtag"
> -                " WHERE patchwork_patchtag.patch_id="
> -                "patchwork_patch.submission_ptr_id"
> +                " WHERE patchwork_patchtag.patch_id=patchwork_patch.id"
>                  " AND patchwork_patchtag.tag_id=%s), 0)")
>              select_params.append(tag.id)
>  
> @@ -424,24 +423,8 @@ class Cover(FilenameMixin, EmailMixin, SubmissionMixin):
>          ]
>  
>  
> -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'),
> -        ]
> -
> -
>  @python_2_unicode_compatible
> -class Patch(Submission):
> -    # patch metadata
> +class Patch(FilenameMixin, EmailMixin, SubmissionMixin):
>  
>      diff = models.TextField(null=True, blank=True)
>      commit_ref = models.CharField(max_length=255, null=True, blank=True)
> @@ -450,24 +433,31 @@ class Patch(Submission):
>  
>      # patchwork metadata
>  
> -    delegate = models.ForeignKey(User, blank=True, null=True,
> -                                 on_delete=models.CASCADE)
> +    delegate = models.ForeignKey(
> +        User,
> +        blank=True,
> +        null=True,
> +        on_delete=models.CASCADE,
> +    )
>      state = models.ForeignKey(State, null=True, on_delete=models.CASCADE)
>      archived = models.BooleanField(default=False)
>      hash = HashField(null=True, blank=True)
>  
> -    # duplicate project from submission in subclass so we can count the
> -    # patches in a project without needing to do a JOIN.
> -    patch_project = models.ForeignKey(Project, on_delete=models.CASCADE)
> -
>      # series metadata
>  
>      series = models.ForeignKey(
> -        'Series', null=True, blank=True, on_delete=models.CASCADE,
> -        related_name='patches', related_query_name='patch')
> +        'Series',
> +        null=True,
> +        blank=True,
> +        on_delete=models.CASCADE,
> +        related_name='patches',
> +        related_query_name='patch',
> +    )
>      number = models.PositiveSmallIntegerField(
> -        default=None, null=True,
> -        help_text='The number assigned to this patch in the series')
> +        default=None,
> +        null=True,
> +        help_text='The number assigned to this patch in the series',
> +    )
>  
>      objects = PatchManager()
>  
> @@ -632,14 +622,23 @@ class Patch(Submission):
>  
>      class Meta:
>          verbose_name_plural = 'Patches'
> +        ordering = ['date']
>          base_manager_name = 'objects'
> -        unique_together = [('series', 'number')]
> -
> +        unique_together = [('msgid', 'project'), ('series', 'number')]
>          indexes = [
>              # This is a covering index for the /list/ query
> -            models.Index(fields=['archived', 'patch_project', 'state',
> -                                 'delegate'],
> -                         name='patch_list_covering_idx'),
> +            models.Index(
> +                fields=[
> +                    'archived',
> +                    'state',
> +                    'delegate',
> +                    'date',
> +                    'project',
> +                    'submitter',
> +                    'name',
> +                ],
> +                name='patch_covering_idx',
> +            ),
>          ]
>  
>  
> @@ -678,7 +677,7 @@ class PatchComment(EmailMixin, models.Model):
>      # parent
>  
>      patch = models.ForeignKey(
> -        Submission,
> +        Patch,
>          related_name='comments',
>          related_query_name='comment',
>          on_delete=models.CASCADE,
> @@ -698,15 +697,11 @@ class PatchComment(EmailMixin, models.Model):
>  
>      def save(self, *args, **kwargs):
>          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()
> +        self.patch.refresh_tag_counts()
>  
>      def delete(self, *args, **kwargs):
>          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()
> +        self.patch.refresh_tag_counts()
>  
>      def is_editable(self, user):
>          return False
> @@ -749,10 +744,10 @@ class Series(FilenameMixin, models.Model):
>  
>      @staticmethod
>      def _format_name(obj):
> -        # The parser ensure 'Submission.name' will always take the form
> -        # 'subject' or '[prefix_a,prefix_b,...] subject'. There will never be
> -        # multiple prefixes (text inside brackets), thus, we don't need to
> -        # account for multiple prefixes here.
> +        # The parser ensure 'Cover.name' will always take the form 'subject' or
> +        # '[prefix_a,prefix_b,...] subject'. There will never be multiple
> +        # prefixes (text inside brackets), thus, we don't need to account for
> +        # multiple prefixes here.
>          prefix_re = re.compile(r'^\[([^\]]*)\]\s*(.*)$')
>          match = prefix_re.match(obj.name)
>          if match:
> @@ -1118,7 +1113,10 @@ class EmailOptout(models.Model):
>  
>  
>  class PatchChangeNotification(models.Model):
> -    patch = models.OneToOneField(Patch, primary_key=True,
> -                                 on_delete=models.CASCADE)
> +    patch = models.OneToOneField(
> +        Patch,
> +        primary_key=True,
> +        on_delete=models.CASCADE,
> +    )
>      last_modified = models.DateTimeField(default=datetime.datetime.utcnow)
>      orig_state = models.ForeignKey(State, on_delete=models.CASCADE)
> diff --git a/patchwork/parser.py b/patchwork/parser.py
> index cd82f3be..69cfb63a 100644
> --- a/patchwork/parser.py
> +++ b/patchwork/parser.py
> @@ -30,7 +30,6 @@ from patchwork.models import Project
>  from patchwork.models import Series
>  from patchwork.models import SeriesReference
>  from patchwork.models import State
> -from patchwork.models import Submission
>  
>  
>  _hunk_re = re.compile(r'^\@\@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? \@\@')
> @@ -647,14 +646,14 @@ def find_comment_content(mail):
>      return None, commentbuf
>  
>  
> -def find_submission_for_comment(project, refs):
> +def find_patch_for_comment(project, refs):
>      for ref in refs:
>          ref = ref[:255]
>          # first, check for a direct reply
>          try:
> -            submission = Submission.objects.get(project=project, msgid=ref)
> -            return submission
> -        except Submission.DoesNotExist:
> +            patch = Patch.objects.get(project=project, msgid=ref)
> +            return patch
> +        except Patch.DoesNotExist:
>              pass
>  
>          # see if we have comments that refer to a patch
> @@ -1099,7 +1098,6 @@ def parse_mail(mail, list_id=None):
>              patch = Patch.objects.create(
>                  msgid=msgid,
>                  project=project,
> -                patch_project=project,
>                  name=name[:255],
>                  date=date,
>                  headers=headers,
> @@ -1273,13 +1271,13 @@ def parse_mail(mail, list_id=None):
>      # comments
>  
>      # we only save comments if we have the parent email
> -    submission = find_submission_for_comment(project, refs)
> -    if submission:
> +    patch = find_patch_for_comment(project, refs)
> +    if patch:
>          author = get_or_create_author(mail, project)
>  
>          try:
>              comment = PatchComment.objects.create(
> -                patch=submission,
> +                patch=patch,
>                  msgid=msgid,
>                  date=date,
>                  headers=headers,
> diff --git a/patchwork/tests/test_mboxviews.py b/patchwork/tests/test_mboxviews.py
> index a7b0186a..1535c5cb 100644
> --- a/patchwork/tests/test_mboxviews.py
> +++ b/patchwork/tests/test_mboxviews.py
> @@ -268,9 +268,12 @@ class MboxSeriesDependencies(TestCase):
>      def test_patch_with_wildcard_series(self):
>          _, patch_a, patch_b = self._create_patches()
>  
> -        response = self.client.get('%s?series=*' % reverse(
> -            'patch-mbox', args=[patch_b.patch.project.linkname,
> -                                patch_b.patch.url_msgid]))
> +        response = self.client.get(
> +            '%s?series=*' % reverse(
> +                'patch-mbox',
> +                args=[patch_b.project.linkname, patch_b.url_msgid],
> +            ),
> +        )
>  
>          self.assertContains(response, patch_a.content)
>          self.assertContains(response, patch_b.content)
> @@ -279,9 +282,12 @@ class MboxSeriesDependencies(TestCase):
>          series, patch_a, patch_b = self._create_patches()
>  
>          response = self.client.get('%s?series=%d' % (
> -            reverse('patch-mbox', args=[patch_b.patch.project.linkname,
> -                                        patch_b.patch.url_msgid]),
> -            series.id))
> +            reverse(
> +                'patch-mbox',
> +                args=[patch_b.project.linkname, patch_b.url_msgid],
> +            ),
> +            series.id,
> +        ))
>  
>          self.assertContains(response, patch_a.content)
>          self.assertContains(response, patch_b.content)
> @@ -291,8 +297,12 @@ class MboxSeriesDependencies(TestCase):
>  
>          for value in ('foo', str(series.id + 1)):
>              response = self.client.get('%s?series=%s' % (
> -                reverse('patch-mbox', args=[patch_b.patch.project.linkname,
> -                                            patch_b.patch.url_msgid]), value))
> +                reverse(
> +                    'patch-mbox',
> +                    args=[patch_b.project.linkname, patch_b.url_msgid]
> +                ),
> +                value,
> +            ))
>  
>              self.assertEqual(response.status_code, 404)
>  
> diff --git a/patchwork/tests/utils.py b/patchwork/tests/utils.py
> index aeeb14d7..b24a992e 100644
> --- a/patchwork/tests/utils.py
> +++ b/patchwork/tests/utils.py
> @@ -190,9 +190,6 @@ def create_patch(**kwargs):
>      }
>      values.update(kwargs)
>  
> -    if 'patch_project' not in values:
> -        values['patch_project'] = values['project']
> -
>      patch = Patch.objects.create(**values)
>  
>      if series:
> @@ -311,7 +308,7 @@ def create_series_reference(**kwargs):
>  
>  
>  def _create_submissions(create_func, count=1, **kwargs):
> -    """Create 'count' Submission-based objects.
> +    """Create 'count' SubmissionMixin-based objects.
>  
>      Args:
>          count (int): Number of patches to create
> diff --git a/patchwork/views/__init__.py b/patchwork/views/__init__.py
> index ad17a070..3efe90cd 100644
> --- a/patchwork/views/__init__.py
> +++ b/patchwork/views/__init__.py
> @@ -257,7 +257,7 @@ def generic_list(request, project, view, view_args=None, filter_settings=None,
>              context['filters'].set_status(filterclass, setting)
>  
>      if patches is None:
> -        patches = Patch.objects.filter(patch_project=project)
> +        patches = Patch.objects.filter(project=project)
>  
>      # annotate with tag counts
>      patches = patches.with_tag_counts(project)
> diff --git a/patchwork/views/utils.py b/patchwork/views/utils.py
> index 875edf45..b301fba9 100644
> --- a/patchwork/views/utils.py
> +++ b/patchwork/views/utils.py
> @@ -183,8 +183,8 @@ def series_to_mbox(series):
>      """
>      mbox = []
>  
> -    for dep in series.patches.all().order_by('number'):
> -        mbox.append(patch_to_mbox(dep.patch))
> +    for patch in series.patches.all().order_by('number'):
> +        mbox.append(patch_to_mbox(patch))
>  
>      return '\n'.join(mbox)
>  
> -- 
> 2.24.1


More information about the Patchwork mailing list