зеркало из https://github.com/mozilla/kitsune.git
[bug 627781] Move object_id back into Watch and test for cascading deletes.
Also add missing migration (accidentally removed in previous commit).
This commit is contained in:
Родитель
ab3758b182
Коммит
b812a931a9
|
@ -2,6 +2,7 @@ import hashlib
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.contenttypes import generic
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from sumo.models import ModelBase, LocaleField
|
from sumo.models import ModelBase, LocaleField
|
||||||
|
@ -58,6 +59,8 @@ class Watch(ModelBase):
|
||||||
|
|
||||||
# Optional reference to a content type:
|
# Optional reference to a content type:
|
||||||
content_type = models.ForeignKey(ContentType, null=True, blank=True)
|
content_type = models.ForeignKey(ContentType, null=True, blank=True)
|
||||||
|
object_id = models.PositiveIntegerField(db_index=True, null=True)
|
||||||
|
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
||||||
|
|
||||||
user = models.ForeignKey(User, null=True, blank=True)
|
user = models.ForeignKey(User, null=True, blank=True)
|
||||||
|
|
||||||
|
@ -81,3 +84,15 @@ class WatchFilter(ModelBase):
|
||||||
# Either ints or hashes of enumerated strings. All we can't represent
|
# Either ints or hashes of enumerated strings. All we can't represent
|
||||||
# easily with this schema is arbitrary (open-vocab) strings.
|
# easily with this schema is arbitrary (open-vocab) strings.
|
||||||
value = models.IntegerField()
|
value = models.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationsMixin(models.Model):
|
||||||
|
"""Mixin for notifications models that adds watches as a generic relation.
|
||||||
|
|
||||||
|
So we get cascading deletes for free, yay!
|
||||||
|
|
||||||
|
"""
|
||||||
|
watches = generic.GenericRelation(Watch)
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
abstract = True
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.db.models import loading
|
||||||
|
|
||||||
from notifications.models import Watch, WatchFilter
|
from notifications.models import Watch, WatchFilter
|
||||||
|
from sumo.tests import TestCase
|
||||||
from users.tests import user
|
from users.tests import user
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,3 +26,34 @@ def watch_filter(save=False, **kwargs):
|
||||||
if save:
|
if save:
|
||||||
f.save()
|
f.save()
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
class ModelsTestCase(TestCase):
|
||||||
|
"""Does some pre-setup and post-teardown work to create tables for any
|
||||||
|
of your test models.
|
||||||
|
|
||||||
|
Simply subclass this and set self.apps to a tuple of *additional*
|
||||||
|
installed apps. These will be added *after* the ones in
|
||||||
|
settings.INSTALLED_APPS.
|
||||||
|
|
||||||
|
Based on http://stackoverflow.com/questions/502916#1827272
|
||||||
|
|
||||||
|
"""
|
||||||
|
apps = []
|
||||||
|
|
||||||
|
def _pre_setup(self):
|
||||||
|
# Add the models to the db.
|
||||||
|
self._original_installed_apps = list(settings.INSTALLED_APPS)
|
||||||
|
for app in self.apps:
|
||||||
|
settings.INSTALLED_APPS.append(app)
|
||||||
|
loading.cache.loaded = False
|
||||||
|
call_command('syncdb', interactive=False, verbosity=0)
|
||||||
|
# Call the original method that does the fixtures etc.
|
||||||
|
super(ModelsTestCase, self)._pre_setup()
|
||||||
|
|
||||||
|
def _post_teardown(self):
|
||||||
|
# Call the original method.
|
||||||
|
super(ModelsTestCase, self)._post_teardown()
|
||||||
|
# Restore the settings.
|
||||||
|
settings.INSTALLED_APPS = self._original_installed_apps
|
||||||
|
loading.cache.loaded = False
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from notifications.models import NotificationsMixin
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: figure out why placing the mixin *after* models.Model fails
|
||||||
|
# See also http://code.djangoproject.com/ticket/10249
|
||||||
|
class MockModel(NotificationsMixin, models.Model):
|
||||||
|
pass
|
|
@ -4,7 +4,8 @@ from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from notifications.events import Event
|
from notifications.events import Event
|
||||||
from notifications.models import Watch
|
from notifications.models import Watch
|
||||||
from notifications.tests import watch, watch_filter
|
from notifications.tests import watch, watch_filter, ModelsTestCase
|
||||||
|
from notifications.tests.models import MockModel
|
||||||
from sumo.tests import TestCase
|
from sumo.tests import TestCase
|
||||||
from users.tests import user
|
from users.tests import user
|
||||||
|
|
||||||
|
@ -140,3 +141,21 @@ class TestNotification(TestCase):
|
||||||
FilteredContentTypeEvent.notify('hi@there.com', color=3, flavor=4)
|
FilteredContentTypeEvent.notify('hi@there.com', color=3, flavor=4)
|
||||||
assert not FilteredContentTypeEvent.is_notifying('hi@there.com',
|
assert not FilteredContentTypeEvent.is_notifying('hi@there.com',
|
||||||
color=3)
|
color=3)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCascadeDelete(ModelsTestCase):
|
||||||
|
"""Cascading deletes on object_id + content_type."""
|
||||||
|
apps = ['notifications.tests']
|
||||||
|
|
||||||
|
def test_mock_model(self):
|
||||||
|
"""Deleting an instance of MockModel should delete watches.
|
||||||
|
|
||||||
|
Create instance of MockModel from notifications.tests.models, then
|
||||||
|
delete it and watch the cascade go.
|
||||||
|
|
||||||
|
"""
|
||||||
|
mock_m = MockModel.objects.create()
|
||||||
|
watch(event_type=TYPE, email='hi@there.com', content_object=mock_m,
|
||||||
|
save=True)
|
||||||
|
MockModel.objects.all().delete()
|
||||||
|
assert not Watch.objects.count(), 'Cascade delete failed.'
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
CREATE TABLE `notifications_watch` (
|
||||||
|
`id` integer NOT NULL AUTO_INCREMENT,
|
||||||
|
`event_type` varchar(30) binary NOT NULL,
|
||||||
|
`content_type_id` integer DEFAULT NULL,
|
||||||
|
`object_id` integer unsigned DEFAULT NULL,
|
||||||
|
`user_id` integer DEFAULT NULL,
|
||||||
|
`email` varchar(75) DEFAULT NULL,
|
||||||
|
`secret` varchar(10) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `notifications_watch_2be07fce` (`event_type`),
|
||||||
|
KEY `notifications_watch_e4470c6e` (`content_type_id`),
|
||||||
|
KEY `notifications_watch_829e37fd` (`object_id`),
|
||||||
|
KEY `notifications_watch_fbfc09f1` (`user_id`),
|
||||||
|
KEY `notifications_watch_3904588a` (`email`),
|
||||||
|
CONSTRAINT `content_type_id_refs_id_23da5933` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
|
||||||
|
CONSTRAINT `user_id_refs_id_2dc6eef1` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `notifications_watchfilter` (
|
||||||
|
`id` integer NOT NULL AUTO_INCREMENT,
|
||||||
|
`watch_id` integer NOT NULL,
|
||||||
|
`name` binary(20) NOT NULL,
|
||||||
|
`value` integer NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `notifications_watchfilter_6e1bd094` (`watch_id`),
|
||||||
|
KEY `notifications_watchfilter_52094d6e` (`name`),
|
||||||
|
CONSTRAINT `watch_id_refs_id_444d6e79` FOREIGN KEY (`watch_id`) REFERENCES `notifications_watch` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
Загрузка…
Ссылка в новой задаче