Message ID | 20170207221244.20960-4-stephen@that.guru |
---|---|
State | Accepted |
Headers | show |
Hi Stephen, "CommandError: Conflicting migrations detected; multiple leaf nodes in the migration graph: (0017_add_event_model, 0017_improved_delegation_rule_docs in patchwork)." The migration has will need to be renumbered before merging. Regards, Daniel > Events record Patch-related things like initial creation, state > transitions, delegation assigning etc. > > Signed-off-by: Stephen Finucane <stephen@that.guru> > --- > Changes since v2: > - Add database indexes to speed up queries on these oft queried fields > - Add new events > - series-completed > - Rename some events > - patch-check-added -> check-added > - patch-dependencies-met -> patch-completed > Changes since v1: > - Add new events > - series-created > - cover-created > These require additional fields on the model > - Rename some events to present a clear parent-child hierarchy > --- > patchwork/migrations/0017_add_event_model.py | 38 +++++++++++ > patchwork/models.py | 96 +++++++++++++++++++++++++++- > 2 files changed, 132 insertions(+), 2 deletions(-) > create mode 100644 patchwork/migrations/0017_add_event_model.py > > diff --git a/patchwork/migrations/0017_add_event_model.py b/patchwork/migrations/0017_add_event_model.py > new file mode 100644 > index 0000000..db10713 > --- /dev/null > +++ b/patchwork/migrations/0017_add_event_model.py > @@ -0,0 +1,38 @@ > +# -*- coding: utf-8 -*- > +from __future__ import unicode_literals > + > +import datetime > +from django.conf import settings > +from django.db import migrations, models > +import django.db.models.deletion > + > + > +class Migration(migrations.Migration): > + > + dependencies = [ > + migrations.swappable_dependency(settings.AUTH_USER_MODEL), > + ('patchwork', '0016_series_project'), > + ] > + > + operations = [ > + migrations.CreateModel( > + name='Event', > + fields=[ > + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), > + ('category', 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'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=20)), > + ('date', models.DateTimeField(default=datetime.datetime.now, help_text=b'The time this event was created.')), > + ('cover', models.ForeignKey(blank=True, help_text=b'The cover letter that this event was created for.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.CoverLetter')), > + ('created_check', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.Check')), > + ('current_delegate', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), > + ('current_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.State')), > + ('patch', 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.Patch')), > + ('previous_delegate', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), > + ('previous_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.State')), > + ('project', models.ForeignKey(help_text=b'The project that the events belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.Project')), > + ('series', models.ForeignKey(blank=True, help_text=b'The series that this event was created for.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.Series')), > + ], > + options={ > + 'ordering': ['-date'], > + }, > + ), > + ] > diff --git a/patchwork/models.py b/patchwork/models.py > index cf363f0..d9f7bec 100644 > --- a/patchwork/models.py > +++ b/patchwork/models.py > @@ -20,14 +20,15 @@ > > from __future__ import absolute_import > > -from collections import Counter, OrderedDict > +from collections import Counter > +from collections import OrderedDict > import datetime > import random > import re > > import django > -from django.contrib.auth.models import User > from django.conf import settings > +from django.contrib.auth.models import User > from django.contrib.sites.models import Site > from django.core.urlresolvers import reverse > from django.db import models > @@ -793,6 +794,97 @@ class Check(models.Model): > return '%s (%s)' % (self.context, self.get_state_display()) > > > +class Event(models.Model): > + """An event raised against a patch. > + > + Events are created whenever certain attributes of a patch are > + changed. > + > + This model makes extensive use of nullification of fields. This is more > + performant than a solution using concrete subclasses while still providing > + the integrity promises that foreign keys provide. Generic foreign keys > + are another solution, but using these will result in a lot of massaging > + should we wish to add support for an 'expand' paramter in the REST API in > + the future. Refer to https://code.djangoproject.com/ticket/24272 for more > + information. > + """ > + CATEGORY_COVER_CREATED = 'cover-created' > + CATEGORY_PATCH_CREATED = 'patch-created' > + CATEGORY_PATCH_COMPLETED = 'patch-completed' > + CATEGORY_PATCH_STATE_CHANGED = 'patch-state-changed' > + CATEGORY_PATCH_DELEGATED = 'patch-delegated' > + CATEGORY_CHECK_CREATED = 'check-created' > + CATEGORY_SERIES_CREATED = 'series-created' > + CATEGORY_SERIES_COMPLETED = 'series-completed' > + CATEGORY_CHOICES = ( > + (CATEGORY_COVER_CREATED, 'Cover Letter Created'), > + (CATEGORY_PATCH_CREATED, 'Patch Created'), > + (CATEGORY_PATCH_COMPLETED, 'Patch Completed'), > + (CATEGORY_PATCH_STATE_CHANGED, 'Patch State Changed'), > + (CATEGORY_PATCH_DELEGATED, 'Patch Delegate Changed'), > + (CATEGORY_CHECK_CREATED, 'Check Created'), > + (CATEGORY_SERIES_CREATED, 'Series Created'), > + (CATEGORY_SERIES_COMPLETED, 'Series Completed'), > + ) > + > + # parents > + > + project = models.ForeignKey( > + Project, related_name='+', db_index=True, > + help_text='The project that the events belongs to.') > + > + # event metadata > + > + category = models.CharField( > + max_length=20, > + choices=CATEGORY_CHOICES, > + db_index=True, > + help_text='The category of the event.') > + date = models.DateTimeField( > + default=datetime.datetime.now, > + help_text='The time this event was created.') > + > + # event object > + > + # only one of the below should be used, depending on which category was > + # used > + > + patch = models.ForeignKey( > + Patch, related_name='+', null=True, blank=True, > + help_text='The patch that this event was created for.') > + series = models.ForeignKey( > + Series, related_name='+', null=True, blank=True, > + help_text='The series that this event was created for.') > + cover = models.ForeignKey( > + CoverLetter, related_name='+', null=True, blank=True, > + help_text='The cover letter that this event was created for.') > + > + # fields for 'patch-state-changed' events > + > + previous_state = models.ForeignKey( > + State, related_name='+', null=True, blank=True) > + current_state = models.ForeignKey( > + State, related_name='+', null=True, blank=True) > + > + # fields for 'patch-delegate-changed' events > + > + previous_delegate = models.ForeignKey( > + User, related_name='+', null=True, blank=True) > + current_delegate = models.ForeignKey( > + User, related_name='+', null=True, blank=True) > + > + # fields or 'patch-check-created' events > + > + created_check = models.ForeignKey( > + Check, related_name='+', null=True, blank=True) > + > + # TODO(stephenfin): Validate that the correct fields are being set by way > + # of a 'clean' method > + > + class Meta: > + ordering = ['-date'] > + > + > class EmailConfirmation(models.Model): > validity = datetime.timedelta(days=settings.CONFIRMATION_VALIDITY_DAYS) > type = models.CharField(max_length=20, choices=[ > -- > 2.9.3 > > _______________________________________________ > Patchwork mailing list > Patchwork@lists.ozlabs.org > https://lists.ozlabs.org/listinfo/patchwork
diff --git a/patchwork/migrations/0017_add_event_model.py b/patchwork/migrations/0017_add_event_model.py new file mode 100644 index 0000000..db10713 --- /dev/null +++ b/patchwork/migrations/0017_add_event_model.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import datetime +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('patchwork', '0016_series_project'), + ] + + operations = [ + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('category', 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'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=20)), + ('date', models.DateTimeField(default=datetime.datetime.now, help_text=b'The time this event was created.')), + ('cover', models.ForeignKey(blank=True, help_text=b'The cover letter that this event was created for.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.CoverLetter')), + ('created_check', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.Check')), + ('current_delegate', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ('current_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.State')), + ('patch', 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.Patch')), + ('previous_delegate', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ('previous_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.State')), + ('project', models.ForeignKey(help_text=b'The project that the events belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.Project')), + ('series', models.ForeignKey(blank=True, help_text=b'The series that this event was created for.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.Series')), + ], + options={ + 'ordering': ['-date'], + }, + ), + ] diff --git a/patchwork/models.py b/patchwork/models.py index cf363f0..d9f7bec 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -20,14 +20,15 @@ from __future__ import absolute_import -from collections import Counter, OrderedDict +from collections import Counter +from collections import OrderedDict import datetime import random import re import django -from django.contrib.auth.models import User from django.conf import settings +from django.contrib.auth.models import User from django.contrib.sites.models import Site from django.core.urlresolvers import reverse from django.db import models @@ -793,6 +794,97 @@ class Check(models.Model): return '%s (%s)' % (self.context, self.get_state_display()) +class Event(models.Model): + """An event raised against a patch. + + Events are created whenever certain attributes of a patch are + changed. + + This model makes extensive use of nullification of fields. This is more + performant than a solution using concrete subclasses while still providing + the integrity promises that foreign keys provide. Generic foreign keys + are another solution, but using these will result in a lot of massaging + should we wish to add support for an 'expand' paramter in the REST API in + the future. Refer to https://code.djangoproject.com/ticket/24272 for more + information. + """ + CATEGORY_COVER_CREATED = 'cover-created' + CATEGORY_PATCH_CREATED = 'patch-created' + CATEGORY_PATCH_COMPLETED = 'patch-completed' + CATEGORY_PATCH_STATE_CHANGED = 'patch-state-changed' + CATEGORY_PATCH_DELEGATED = 'patch-delegated' + CATEGORY_CHECK_CREATED = 'check-created' + CATEGORY_SERIES_CREATED = 'series-created' + CATEGORY_SERIES_COMPLETED = 'series-completed' + CATEGORY_CHOICES = ( + (CATEGORY_COVER_CREATED, 'Cover Letter Created'), + (CATEGORY_PATCH_CREATED, 'Patch Created'), + (CATEGORY_PATCH_COMPLETED, 'Patch Completed'), + (CATEGORY_PATCH_STATE_CHANGED, 'Patch State Changed'), + (CATEGORY_PATCH_DELEGATED, 'Patch Delegate Changed'), + (CATEGORY_CHECK_CREATED, 'Check Created'), + (CATEGORY_SERIES_CREATED, 'Series Created'), + (CATEGORY_SERIES_COMPLETED, 'Series Completed'), + ) + + # parents + + project = models.ForeignKey( + Project, related_name='+', db_index=True, + help_text='The project that the events belongs to.') + + # event metadata + + category = models.CharField( + max_length=20, + choices=CATEGORY_CHOICES, + db_index=True, + help_text='The category of the event.') + date = models.DateTimeField( + default=datetime.datetime.now, + help_text='The time this event was created.') + + # event object + + # only one of the below should be used, depending on which category was + # used + + patch = models.ForeignKey( + Patch, related_name='+', null=True, blank=True, + help_text='The patch that this event was created for.') + series = models.ForeignKey( + Series, related_name='+', null=True, blank=True, + help_text='The series that this event was created for.') + cover = models.ForeignKey( + CoverLetter, related_name='+', null=True, blank=True, + help_text='The cover letter that this event was created for.') + + # fields for 'patch-state-changed' events + + previous_state = models.ForeignKey( + State, related_name='+', null=True, blank=True) + current_state = models.ForeignKey( + State, related_name='+', null=True, blank=True) + + # fields for 'patch-delegate-changed' events + + previous_delegate = models.ForeignKey( + User, related_name='+', null=True, blank=True) + current_delegate = models.ForeignKey( + User, related_name='+', null=True, blank=True) + + # fields or 'patch-check-created' events + + created_check = models.ForeignKey( + Check, related_name='+', null=True, blank=True) + + # TODO(stephenfin): Validate that the correct fields are being set by way + # of a 'clean' method + + class Meta: + ordering = ['-date'] + + class EmailConfirmation(models.Model): validity = datetime.timedelta(days=settings.CONFIRMATION_VALIDITY_DAYS) type = models.CharField(max_length=20, choices=[
Events record Patch-related things like initial creation, state transitions, delegation assigning etc. Signed-off-by: Stephen Finucane <stephen@that.guru> --- Changes since v2: - Add database indexes to speed up queries on these oft queried fields - Add new events - series-completed - Rename some events - patch-check-added -> check-added - patch-dependencies-met -> patch-completed Changes since v1: - Add new events - series-created - cover-created These require additional fields on the model - Rename some events to present a clear parent-child hierarchy --- patchwork/migrations/0017_add_event_model.py | 38 +++++++++++ patchwork/models.py | 96 +++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 patchwork/migrations/0017_add_event_model.py