[PATCH v5] models, templates: Add submission relations
Daniel Axtens
dja at axtens.net
Sun Jan 26 23:49:14 AEDT 2020
Daniel Axtens <dja at axtens.net> writes:
> Introduces the ability to add relations between submissions. Relations
> are displayed in the details page of a submission under 'Related'.
> Related submissions located in another projects can be viewed as well.
>
> Changes to relations are tracked in events. Currently the display of
> this is very bare in the API but that will be fixed in a subsequent patch:
> this is the minimum required to avoid throwing errors when you view the
> events feed.
So it occurs to me that teeeeeeeechnically we should probably hide these
events completely in API < 1.2 because an API consumer doesn't know
about them... I would have no idea how to do this though, nor am I sure
if it's a worthwhile exercise... Thoughts?
> Signed-off-by: Mete Polat <metepolat2000 at gmail.com>
> [dja: address some review comments from Stephen, add an admin view,
> move to using Events, misc tidy-ups.]
> Signed-off-by: Daniel Axtens <dja at axtens.net>
>
> ---
>
> I wanted to get this out asap, I'll start on the REST patch next.
> ---
> patchwork/admin.py | 8 ++++
> patchwork/api/event.py | 5 ++-
> .../migrations/0040_add_related_patches.py | 42 +++++++++++++++++++
> patchwork/models.py | 32 +++++++++++++-
> patchwork/signals.py | 24 +++++++++++
> patchwork/templates/patchwork/submission.html | 37 ++++++++++++++++
> patchwork/views/patch.py | 13 ++++++
> 7 files changed, 159 insertions(+), 2 deletions(-)
> create mode 100644 patchwork/migrations/0040_add_related_patches.py
>
> diff --git a/patchwork/admin.py b/patchwork/admin.py
> index f9a94c6f5c07..c3d45240f1eb 100644
> --- a/patchwork/admin.py
> +++ b/patchwork/admin.py
> @@ -14,6 +14,7 @@ from patchwork.models import Comment
> from patchwork.models import CoverLetter
> from patchwork.models import DelegationRule
> from patchwork.models import Patch
> +from patchwork.models import PatchRelation
> from patchwork.models import Person
> from patchwork.models import Project
> from patchwork.models import Series
> @@ -174,3 +175,10 @@ class TagAdmin(admin.ModelAdmin):
>
>
> admin.site.register(Tag, TagAdmin)
> +
> +
> +class PatchRelationAdmin(admin.ModelAdmin):
> + model = PatchRelation
> +
> +
> +admin.site.register(PatchRelation, PatchRelationAdmin)
> diff --git a/patchwork/api/event.py b/patchwork/api/event.py
> index a066faaec63b..d7685f4c138c 100644
> --- a/patchwork/api/event.py
> +++ b/patchwork/api/event.py
> @@ -42,6 +42,8 @@ class EventSerializer(ModelSerializer):
> 'current_state'],
> Event.CATEGORY_PATCH_DELEGATED: ['patch', 'previous_delegate',
> 'current_delegate'],
> + Event.CATEGORY_PATCH_RELATION_CHANGED: ['patch', 'previous_relation',
> + 'current_relation'],
> Event.CATEGORY_CHECK_CREATED: ['patch', 'created_check'],
> Event.CATEGORY_SERIES_CREATED: ['series'],
> Event.CATEGORY_SERIES_COMPLETED: ['series'],
> @@ -68,7 +70,8 @@ class EventSerializer(ModelSerializer):
> model = Event
> fields = ('id', 'category', 'project', 'date', 'actor', 'patch',
> 'series', 'cover', 'previous_state', 'current_state',
> - 'previous_delegate', 'current_delegate', 'created_check')
> + 'previous_delegate', 'current_delegate', 'created_check',
> + 'previous_relation', 'current_relation',)
> read_only_fields = fields
> versioned_fields = {
> '1.2': ('actor', ),
> diff --git a/patchwork/migrations/0040_add_related_patches.py b/patchwork/migrations/0040_add_related_patches.py
> new file mode 100644
> index 000000000000..f4d811fedddd
> --- /dev/null
> +++ b/patchwork/migrations/0040_add_related_patches.py
> @@ -0,0 +1,42 @@
> +# -*- coding: utf-8 -*-
> +# Generated by Django 1.11.27 on 2020-01-15 23:15
> +from __future__ import unicode_literals
> +
> +from django.db import migrations, models
> +import django.db.models.deletion
> +
> +
> +class Migration(migrations.Migration):
> +
> + dependencies = [
> + ('patchwork', '0039_unique_series_references'),
> + ]
> +
> + operations = [
> + migrations.CreateModel(
> + name='PatchRelation',
> + fields=[
> + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
> + ],
> + ),
> + migrations.AlterField(
> + model_name='event',
> + name='category',
> + field=models.CharField(choices=[(b'cover-created', b'Cover Letter Created'), (b'patch-created', b'Patch Created'), (b'patch-completed', b'Patch Completed'), (b'patch-state-changed', b'Patch State Changed'), (b'patch-delegated', b'Patch Delegate Changed'), (b'patch-relation-changed', b'Patch Relation Changed'), (b'check-created', b'Check Created'), (b'series-created', b'Series Created'), (b'series-completed', b'Series Completed')], db_index=True, help_text=b'The category of the event.', max_length=25),
> + ),
> + migrations.AddField(
> + model_name='event',
> + name='current_relation',
> + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.PatchRelation'),
> + ),
> + migrations.AddField(
> + model_name='event',
> + name='previous_relation',
> + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.PatchRelation'),
> + ),
> + migrations.AddField(
> + model_name='patch',
> + 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'),
> + ),
> + ]
> diff --git a/patchwork/models.py b/patchwork/models.py
> index be71d4077892..4f7186d3ec10 100644
> --- a/patchwork/models.py
> +++ b/patchwork/models.py
> @@ -447,6 +447,12 @@ class Patch(Submission):
> default=None, null=True,
> help_text='The number assigned to this patch in the series')
>
> + # related patches metadata
> +
> + related = models.ForeignKey(
> + 'PatchRelation', null=True, blank=True, on_delete=models.SET_NULL,
> + related_name='patches', related_query_name='patch')
> +
> objects = PatchManager()
>
> @staticmethod
> @@ -863,6 +869,19 @@ class BundlePatch(models.Model):
> ordering = ['order']
>
>
> + at python_2_unicode_compatible
> +class PatchRelation(models.Model):
> +
> + def __str__(self):
> + patches = self.patches.all()
> + if not patches:
> + return '<Empty>'
> + name = ', '.join(patch.name for patch in patches[:10])
> + if len(name) > 60:
> + name = name[:60] + '...'
> + return name
> +
> +
> @python_2_unicode_compatible
> class Check(models.Model):
>
> @@ -928,6 +947,7 @@ class Event(models.Model):
> CATEGORY_PATCH_COMPLETED = 'patch-completed'
> CATEGORY_PATCH_STATE_CHANGED = 'patch-state-changed'
> CATEGORY_PATCH_DELEGATED = 'patch-delegated'
> + CATEGORY_PATCH_RELATION_CHANGED = 'patch-relation-changed'
> CATEGORY_CHECK_CREATED = 'check-created'
> CATEGORY_SERIES_CREATED = 'series-created'
> CATEGORY_SERIES_COMPLETED = 'series-completed'
> @@ -937,6 +957,7 @@ class Event(models.Model):
> (CATEGORY_PATCH_COMPLETED, 'Patch Completed'),
> (CATEGORY_PATCH_STATE_CHANGED, 'Patch State Changed'),
> (CATEGORY_PATCH_DELEGATED, 'Patch Delegate Changed'),
> + (CATEGORY_PATCH_RELATION_CHANGED, 'Patch Relation Changed'),
> (CATEGORY_CHECK_CREATED, 'Check Created'),
> (CATEGORY_SERIES_CREATED, 'Series Created'),
> (CATEGORY_SERIES_COMPLETED, 'Series Completed'),
> @@ -952,7 +973,7 @@ class Event(models.Model):
> # event metadata
>
> category = models.CharField(
> - max_length=20,
> + max_length=25,
> choices=CATEGORY_CHOICES,
> db_index=True,
> help_text='The category of the event.')
> @@ -1000,6 +1021,15 @@ class Event(models.Model):
> User, related_name='+', null=True, blank=True,
> on_delete=models.CASCADE)
>
> + # fields for 'patch-relation-changed-changed' events
> +
> + previous_relation = models.ForeignKey(
> + PatchRelation, related_name='+', null=True, blank=True,
> + on_delete=models.CASCADE)
> + current_relation = models.ForeignKey(
> + PatchRelation, related_name='+', null=True, blank=True,
> + on_delete=models.CASCADE)
> +
> # fields or 'patch-check-created' events
>
> created_check = models.ForeignKey(
> diff --git a/patchwork/signals.py b/patchwork/signals.py
> index 73ddfa5e35ee..3a2f0fbdd3a4 100644
> --- a/patchwork/signals.py
> +++ b/patchwork/signals.py
> @@ -134,6 +134,30 @@ def create_patch_delegated_event(sender, instance, raw, **kwargs):
> create_event(instance, orig_patch.delegate, instance.delegate)
>
>
> + at receiver(pre_save, sender=Patch)
> +def create_patch_relation_changed_event(sender, instance, raw, **kwargs):
> +
> + def create_event(patch, before, after):
> + return Event.objects.create(
> + category=Event.CATEGORY_PATCH_RELATION_CHANGED,
> + project=patch.project,
> + actor=getattr(patch, '_edited_by', None),
> + patch=patch,
> + previous_relation=before,
> + current_relation=after)
> +
> + # don't trigger for items loaded from fixtures or new items
> + if raw or not instance.pk:
> + return
> +
> + orig_patch = Patch.objects.get(pk=instance.pk)
> +
> + if orig_patch.related == instance.related:
> + return
> +
> + create_event(instance, orig_patch.related, instance.related)
> +
> +
> @receiver(pre_save, sender=Patch)
> def create_patch_completed_event(sender, instance, raw, **kwargs):
>
> diff --git a/patchwork/templates/patchwork/submission.html b/patchwork/templates/patchwork/submission.html
> index 77a2711ab5b4..978559b8726b 100644
> --- a/patchwork/templates/patchwork/submission.html
> +++ b/patchwork/templates/patchwork/submission.html
> @@ -110,6 +110,43 @@ function toggle_div(link_id, headers_id, label_show, label_hide)
> </td>
> </tr>
> {% endif %}
> +{% if submission.related %}
> + <tr>
> + <th>Related</th>
> + <td>
> + <a id="togglerelated"
> + href="javascript:toggle_div('togglerelated', 'related')"
> + >show</a>
> + <div id="related" class="submissionlist" style="display:none;">
> + <ul>
> + {% for sibling in related_same_project %}
> + <li>
> + {% if sibling.id != submission.id %}
> + <a href="{% url 'patch-detail' project_id=project.linkname msgid=sibling.url_msgid %}">
> + {{ sibling.name|default:"[no subject]"|truncatechars:100 }}
> + </a>
> + {% endif %}
> + </li>
> + {% endfor %}
> + {% if related_different_project %}
> + <a id="togglerelatedoutside"
> + href="javascript:toggle_div('togglerelatedoutside', 'relatedoutside', 'show from other projects')"
> + >show from other projects</a>
> + <div id="relatedoutside" class="submissionlist" style="display:none;">
> + {% for sibling in related_outside %}
> + <li>
> + <a href="{% url 'patch-detail' project_id=sibling.project.linkname msgid=sibling.url_msgid %}">
> + {{ sibling.name|default:"[no subject]"|truncatechars:100 }}
> + </a> (in {{ sibling.project }})
> + </li>
> + {% endfor %}
> + </div>
> + {% endif %}
> + </ul>
> + </div>
> + </td>
> + </tr>
> +{% endif %}
> </table>
>
> <div class="patchforms">
> diff --git a/patchwork/views/patch.py b/patchwork/views/patch.py
> index f34053ce57da..e32ff0bff5f5 100644
> --- a/patchwork/views/patch.py
> +++ b/patchwork/views/patch.py
> @@ -110,12 +110,25 @@ def patch_detail(request, project_id, msgid):
> comments = comments.only('submitter', 'date', 'id', 'content',
> 'submission')
>
> + if patch.related:
> + related_same_project = \
> + patch.related.patches.only('name', 'msgid', 'project', 'related')
> + # avoid a second trip out to the db for info we already have
> + related_different_project = \
> + [related_patch for related_patch in related_same_project
> + if related_patch.project_id != patch.project_id]
> + else:
> + related_same_project = []
> + related_different_project = []
> +
> context['comments'] = comments
> context['checks'] = patch.check_set.all().select_related('user')
> context['submission'] = patch
> context['patchform'] = form
> context['createbundleform'] = createbundleform
> context['project'] = patch.project
> + context['related_same_project'] = related_same_project
> + context['related_different_project'] = related_different_project
>
> return render(request, 'patchwork/submission.html', context)
>
> --
> 2.20.1
More information about the Patchwork
mailing list