aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--patchwork/admin.py8
-rw-r--r--patchwork/api/event.py5
-rw-r--r--patchwork/migrations/0040_add_related_patches.py41
-rw-r--r--patchwork/models.py32
-rw-r--r--patchwork/signals.py24
-rw-r--r--patchwork/templates/patchwork/submission.html37
-rw-r--r--patchwork/views/patch.py14
7 files changed, 159 insertions, 2 deletions
diff --git a/patchwork/admin.py b/patchwork/admin.py
index f9a94c6..c3d4524 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 fdff6a4..44c3452 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 0000000..fc2994d
--- /dev/null
+++ b/patchwork/migrations/0040_add_related_patches.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+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 08ee341..e295e17 100644
--- a/patchwork/models.py
+++ b/patchwork/models.py
@@ -449,6 +449,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
@@ -866,6 +872,19 @@ class BundlePatch(models.Model):
@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):
"""Check for a patch.
@@ -930,6 +949,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'
@@ -939,6 +959,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'),
@@ -954,7 +975,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.')
@@ -1002,6 +1023,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 73ddfa5..3a2f0fb 100644
--- a/patchwork/signals.py
+++ b/patchwork/signals.py
@@ -135,6 +135,30 @@ def create_patch_delegated_event(sender, instance, raw, **kwargs):
@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):
def create_event(patch):
diff --git a/patchwork/templates/patchwork/submission.html b/patchwork/templates/patchwork/submission.html
index 77a2711..978559b 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 b368cfa..470ad19 100644
--- a/patchwork/views/patch.py
+++ b/patchwork/views/patch.py
@@ -110,12 +110,26 @@ 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)