diff options
-rw-r--r-- | patchwork/api/comment.py | 4 | ||||
-rw-r--r-- | patchwork/api/filters.py | 3 | ||||
-rw-r--r-- | patchwork/management/commands/dumparchive.py | 2 | ||||
-rw-r--r-- | patchwork/migrations/0043_merge_patch_submission.py | 292 | ||||
-rw-r--r-- | patchwork/models.py | 94 | ||||
-rw-r--r-- | patchwork/parser.py | 16 | ||||
-rw-r--r-- | patchwork/tests/test_mboxviews.py | 26 | ||||
-rw-r--r-- | patchwork/tests/utils.py | 5 | ||||
-rw-r--r-- | patchwork/views/__init__.py | 2 | ||||
-rw-r--r-- | patchwork/views/utils.py | 4 |
10 files changed, 371 insertions, 77 deletions
diff --git a/patchwork/api/comment.py b/patchwork/api/comment.py index 3802dab..43b26c6 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/api/filters.py b/patchwork/api/filters.py index bd3b326..d9b65a8 100644 --- a/patchwork/api/filters.py +++ b/patchwork/api/filters.py @@ -205,8 +205,7 @@ class CoverFilterSet(TimestampMixin, BaseFilterSet): class PatchFilterSet(TimestampMixin, BaseFilterSet): - project = ProjectFilter(queryset=Project.objects.all(), distinct=False, - field_name='patch_project') + project = ProjectFilter(queryset=Project.objects.all(), distinct=False) # NOTE(stephenfin): We disable the select-based HTML widgets for these # filters as the resulting query is _huge_ series = BaseFilter(queryset=Series.objects.all(), diff --git a/patchwork/management/commands/dumparchive.py b/patchwork/management/commands/dumparchive.py index 9ee80c8..e9445ea 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/0043_merge_patch_submission.py b/patchwork/migrations/0043_merge_patch_submission.py new file mode 100644 index 0000000..25e741d --- /dev/null +++ b/patchwork/migrations/0043_merge_patch_submission.py @@ -0,0 +1,292 @@ +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, + related_id = patchwork_patch.related2_id, + 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.related_id = patchwork_patch.related2_id, + 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', '0042_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='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='related', new_name='related2', + ), + 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='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='related', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='patches', + related_query_name='patch', + to='patchwork.PatchRelation', + ), + ), + 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=[ + 'archived', + 'state', + 'delegate', + 'date', + 'project', + 'submitter', + 'name', + ], + name='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='related2',), + 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' + + migrations.DeleteModel(name='Patch',), + migrations.RenameModel(old_name='Submission', new_name='Patch',), + ] diff --git a/patchwork/models.py b/patchwork/models.py index 3755b65..a5e7bea 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -164,7 +164,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 @@ -285,8 +285,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) @@ -415,23 +414,7 @@ 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'), - ] - - -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) @@ -440,24 +423,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', + ) # related patches metadata @@ -628,14 +618,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', + ), ] @@ -674,7 +673,7 @@ class PatchComment(EmailMixin, models.Model): # parent patch = models.ForeignKey( - Submission, + Patch, related_name='comments', related_query_name='comment', on_delete=models.CASCADE, @@ -694,15 +693,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 @@ -744,10 +739,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: @@ -1133,7 +1128,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 3e2d2ff..6d33bcd 100644 --- a/patchwork/parser.py +++ b/patchwork/parser.py @@ -29,7 +29,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+))? \@\@') @@ -653,14 +652,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 @@ -1104,7 +1103,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, @@ -1277,15 +1275,15 @@ 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) with transaction.atomic(): if PatchComment.objects.filter(patch=patch, msgid=msgid): raise DuplicateMailError(msgid=msgid) 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 a7b0186..1535c5c 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 83bd66a..c464979 100644 --- a/patchwork/tests/utils.py +++ b/patchwork/tests/utils.py @@ -191,9 +191,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: @@ -312,7 +309,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 ad17a07..3efe90c 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 f02948c..2bf6525 100644 --- a/patchwork/views/utils.py +++ b/patchwork/views/utils.py @@ -179,8 +179,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) |