Commit d97ca94a authored by Damien Lespiau's avatar Damien Lespiau
Browse files

series: Store the last revision in the series object



I've been postponing this for some time, but it's better to have a
foreign key to the latest series than caching some of its data on the
Series object.

We still want that link on Series, because retrieving the data held on
the revision is a simple JOIN, so we can still have our O(1) number of
SQL queries when retrieving data for the series list page.

While at it and to limit the number of migration steps, we move the
n_patches to SeriesRevision, where it should have been in the first
place. Also the version field can simply be the one on last_revision
now.
Signed-off-by: default avatarDamien Lespiau <damien.lespiau@intel.com>
parent fe3f37a7
......@@ -399,7 +399,7 @@ def find_content(project, mail):
(ret.series, ret.revision, ret.patch_order, n) = \
find_series_for_mail(project, series_name, msgid, is_patch,
ret.patch_order, n, refs)
ret.series.n_patches = n or 1
ret.revision.n_patches = n or 1
date = mail_date(mail)
if not ret.series.submitted or date < ret.series.submitted:
......@@ -477,7 +477,7 @@ def find_patch_order(revisions, previous_patch, order, n_patches):
order = SeriesRevisionPatch.objects.get(revision=revision,
patch=previous_patch).order
if n_patches is None:
n_patches = revision.series.n_patches
n_patches = revision.n_patches
break
except SeriesRevisionPatch.DoesNotExist:
continue
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
def update_last_revision_n_patches(apps, schema_editor):
Series = apps.get_model("patchwork", "Series")
SeriesRevision = apps.get_model("patchwork", "SeriesRevision")
query = Series.objects.all()
for _, series in enumerate(query.iterator()):
revisions = SeriesRevision.objects.filter(series=series). \
order_by('version').reverse()
for field in series._meta.local_fields:
if field.name == "last_updated":
field.auto_now = False
series.last_revision = revisions[0]
series.save()
for field in series._meta.local_fields:
if field.name == "last_updated":
field.auto_now = True
for revision in revisions:
revision.n_patches = series.n_patches
revision.save()
def noop(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('patchwork', '0013_seriesrevision_test_state'),
]
operations = [
migrations.AddField(
model_name='series',
name='last_revision',
field=models.OneToOneField(related_name='+', null=True, to='patchwork.SeriesRevision'),
),
migrations.AddField(
model_name='seriesrevision',
name='n_patches',
field=models.IntegerField(default=0),
),
migrations.RunPython(update_last_revision_n_patches, noop),
]
......@@ -475,6 +475,10 @@ class Series(models.Model):
version = models.IntegerField(default=1)
# This is the number of patches of the latest version.
n_patches = models.IntegerField(default=0)
# direct access to the latest revision so we can get the latest revision
# information with a JOIN
last_revision = models.OneToOneField('SeriesRevision', null=True,
related_name='+')
def __unicode__(self):
return self.name
......@@ -517,6 +521,7 @@ class SeriesRevision(models.Model):
version = models.IntegerField(default=1)
root_msgid = models.CharField(max_length=255)
cover_letter = models.TextField(null = True, blank = True)
n_patches = models.IntegerField(default=0)
patches = models.ManyToManyField(Patch, through = 'SeriesRevisionPatch')
test_state = models.SmallIntegerField(choices=TestState.STATE_CHOICES,
null=True)
......@@ -538,7 +543,7 @@ class SeriesRevision(models.Model):
order=order)
sp.save()
revision_complete = self.patches.count() == self.series.n_patches
revision_complete = self.patches.count() == self.n_patches
if revision_complete:
series_revision_complete.send(sender=self.__class__, revision=self)
......@@ -550,10 +555,6 @@ class SeriesRevision(models.Model):
new.test_state = None
new.save()
series = new.series
series.version = new.version
series.save()
return new
def duplicate(self, exclude_patches=()):
......@@ -751,9 +752,16 @@ def _patch_change_callback(sender, instance, **kwargs):
models.signals.pre_save.connect(_patch_change_callback, sender = Patch)
def _on_revision_complete(sender, revision, **kwargs):
series = revision.series
# update series.last_revision
series.last_revision = series.latest_revision()
series.save()
# log event
new_revision = Event.objects.get(name='series-new-revision')
log = EventLog(event=new_revision, series=revision.series,
user=revision.series.submitter.user,
log = EventLog(event=new_revision, series=series,
user=series.submitter.user,
parameters={'revision': revision.version})
log.save()
......
......@@ -127,10 +127,14 @@ class StateSerializer(serializers.ModelSerializer):
fields = ('id', 'name')
class SeriesSerializer(PatchworkModelSerializer):
version = serializers.IntegerField(source='last_revision.version',
read_only=True)
n_patches = serializers.IntegerField(source='last_revision.n_patches',
read_only=True)
test_state = serializers.SerializerMethodField('get_test_state')
def get_test_state(self, obj):
state = obj.latest_revision().test_state
state = obj.last_revision.test_state
if state is not None:
return dict(TestState.STATE_CHOICES)[state]
return state
......@@ -140,8 +144,8 @@ class SeriesSerializer(PatchworkModelSerializer):
fields = ('id', 'project', 'name', 'n_patches', 'submitter',
'submitted', 'last_updated', 'version', 'reviewer',
'test_state')
read_only_fields = ('project', 'n_patches', 'submitter', 'submitted',
'last_updated', 'version')
read_only_fields = ('project', 'submitter', 'submitted',
'last_updated')
expand_serializers = {
'project': ProjectSerializer,
'submitter': PersonSerializer,
......
......@@ -39,7 +39,7 @@ $(function () {
</tr>
<tr>
<th>Revision</th>
<td>{{ series.version }}</td>
<td>{{ series.last_revision.version }}</td>
</tr>
</table>
......
......@@ -707,22 +707,25 @@ class TestResultTest(APITestBase):
u"✓ super test: success for " + test[1])
mail.outbox = []
def _test_state(self, serializer, series):
return serializer.get_test_state(Series.objects.get(pk=series.pk))
def testRevisionTestStatus(self):
ss = SeriesSerializer()
self.assertEqual(TestResult.objects.all().count(), 0)
self.assertEqual(ss.get_test_state(self.series), None)
self.assertEqual(self._test_state(ss, self.series), None)
self._post_result(self.rev_url, "test1", 'pending')
self.assertEqual(ss.get_test_state(self.series), 'pending')
self.assertEqual(self._test_state(ss, self.series), 'pending')
self._post_result(self.rev_url, "test2", 'success')
self.assertEqual(ss.get_test_state(self.series), 'success')
self.assertEqual(self._test_state(ss, self.series), 'success')
self._post_result(self.rev_url, "test3", 'warning')
self.assertEqual(ss.get_test_state(self.series), 'warning')
self.assertEqual(self._test_state(ss, self.series), 'warning')
self._post_result(self.rev_url, "test4", 'failure')
self.assertEqual(ss.get_test_state(self.series), 'failure')
self.assertEqual(self._test_state(ss, self.series), 'failure')
# Create a new revision
rev1 = SeriesRevision.objects.get(series=self.series, version=1)
......@@ -730,5 +733,5 @@ class TestResultTest(APITestBase):
rev2.save()
self.assertEqual(self.series.revisions().count(), 2)
self.assertEqual(ss.get_test_state(self.series), None,
self.assertEqual(self._test_state(ss, self.series), None,
"'None' expected as a new revision must reset the testing state")
......@@ -304,7 +304,7 @@ class Series0030(IntelGfxTest):
class UpdatedPatchTest(Series0030):
def testNewRevision(self):
series = Series.objects.all()[0]
self.assertEquals(series.version, 2)
self.assertEquals(series.last_revision.version, 2)
revisions = SeriesRevision.objects.all()
self.assertEquals(revisions.count(), 2)
......@@ -333,7 +333,7 @@ class SinglePatchUpdateTest(GeneratedSeriesTest):
def check(self, original_mails, patch_v2_mail, n):
self.assertEquals(Series.objects.count(), 1)
series = Series.objects.all()[0]
self.assertEquals(series.version, 2)
self.assertEquals(series.last_revision.version, 2)
revisions = SeriesRevision.objects.all()
self.assertEquals(revisions.count(), 2)
......@@ -437,7 +437,7 @@ class SinglePatchUpdatesVariousCornerCasesTest(TestCase):
self.assertEquals(Series.objects.count(), 1)
series = Series.objects.all()[0]
self.assertEquals(series.version, 3)
self.assertEquals(series.last_revision.version, 3)
revisions = SeriesRevision.objects.all()
self.assertEquals(revisions.count(), 3)
......@@ -474,7 +474,7 @@ class SinglePatchUpdatesVariousCornerCasesTest(TestCase):
self.assertEquals(Series.objects.count(), 1)
series = Series.objects.all()[0]
self.assertEquals(series.version, 2)
self.assertEquals(series.last_revision.version, 2)
revisions = SeriesRevision.objects.all()
self.assertEquals(revisions.count(), 2)
......@@ -565,7 +565,7 @@ class FullSeriesUpdateTest(GeneratedSeriesTest):
def check(self, series1_mails, series2_mails):
self.assertEquals(Series.objects.count(), 1)
series = Series.objects.all()[0]
self.assertEquals(series.version, 2)
self.assertEquals(series.last_revision.version, 2)
revisions = SeriesRevision.objects.all()
self.assertEquals(revisions.count(), 2)
......
......@@ -159,18 +159,20 @@ class SeriesFilter(django_filters.FilterSet):
class SeriesListMixin(ListMixin):
queryset = Series.objects.all()
serializer_class = SeriesSerializer
select_fields = ('project', 'submitter', 'reviewer')
select_fields = ('last_revision', )
select_fields__expand = ('project', 'submitter', 'reviewer')
filter_backends = (RequestDjangoFilterBackend, RelatedOrderingFilter)
filter_class = SeriesFilter
class SelectRelatedMixin(object):
def select_related(self, queryset):
select_fields = getattr(self, 'select_fields', None)
if not select_fields:
return queryset
select_fields = getattr(self, 'select_fields', ())
related = self.request.QUERY_PARAMS.get('related')
if not related:
if related:
select_fields += getattr(self, 'select_fields__expand', ())
if not select_fields:
return queryset
return queryset.select_related(*select_fields)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment