diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 7bfe3c294..8f9c1ca87 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -39,6 +39,8 @@ Changelog * Date / time pickers now consistently use times without seconds, to prevent Javascript behaviour glitches when focusing / unfocusing fields * Added hooks `register_rich_text_embed_handler` and `register_rich_text_link_handler` for customising link / embed handling within rich text fields * Added hook `construct_homepage_summary_items` for customising the site summary panel on the admin homepage + * No longer automatically tries to use Celery for sending notification emails + * Added "Add child page" button to admin userbar (Eric Drechsel) 0.8.6 (10.03.2015) diff --git a/docs/howto/performance.rst b/docs/howto/performance.rst index 924e49d9f..0e1b64f68 100644 --- a/docs/howto/performance.rst +++ b/docs/howto/performance.rst @@ -28,21 +28,6 @@ We recommend `Redis `_ as a fast, persistent cache. Install Re Without a persistent cache, Wagtail will recreate all compressable assets at each server start, e.g. when any files change under ``./manage.py runserver``. -Sending emails in the background using Celery ---------------------------------------------- - -Various actions in the Wagtail admin backend can trigger notification emails - for example, submitting a page for moderation. In Wagtail's default configuration, these are sent as part of the page request/response cycle, which means that web server threads can get tied up for long periods if many emails are being sent. To avoid this, Wagtail can be configured to do this as a background task, using `Celery `_ as a task queue. To install Celery, add ``django-celery`` to your requirements.txt. A sample configuration, using Redis as the queue backend, would look like:: - - import djcelery - - djcelery.setup_loader() - - CELERY_SEND_TASK_ERROR_EMAILS = True - BROKER_URL = 'redis://' - -See the Celery documentation for instructions on running the worker process in development or production. - - Search ------ diff --git a/docs/pages/streamfield.rst b/docs/pages/streamfield.rst index 2b8a03bdf..3a3abfa93 100644 --- a/docs/pages/streamfield.rst +++ b/docs/pages/streamfield.rst @@ -285,10 +285,10 @@ Any block type is valid as the sub-block type, including structural types: .. code-block:: python - ('ingredients_list', blocks.ListBlock(blocks.StructBlock( + ('ingredients_list', blocks.ListBlock(blocks.StructBlock([ ('ingredient', blocks.CharBlock(required=True)), ('amount', blocks.CharBlock()), - ))) + ]))) StreamBlock @@ -305,9 +305,9 @@ A block consisting of a sequence of sub-blocks of different types, which can be ('image', ImageChooserBlock()), ('quotation', blocks.StructBlock([ ('text', blocks.TextBlock()), - ('author', blocks.CharBlock), + ('author', blocks.CharBlock()), ])), - ('video', blocks.EmbedBlock()), + ('video', EmbedBlock()), ], icon='cogs' )) @@ -321,9 +321,9 @@ As with StructBlock, the list of sub-blocks can also be provided as a subclass o image = ImageChooserBlock() quotation = blocks.StructBlock([ ('text', blocks.TextBlock()), - ('author', blocks.CharBlock), + ('author', blocks.CharBlock()), ]) - video = blocks.EmbedBlock + video = EmbedBlock() class Meta: icon='cogs' diff --git a/docs/releases/1.0.rst b/docs/releases/1.0.rst index ce96925c8..ccd642f18 100644 --- a/docs/releases/1.0.rst +++ b/docs/releases/1.0.rst @@ -51,6 +51,7 @@ Admin * Added pagination to the snippets listing and chooser * Page / document / image / snippet choosers now include a link to edit the chosen item * Plain text fields in the page editor now use auto-expanding text areas + * Added "Add child page" button to admin userbar **Page editor** @@ -85,6 +86,7 @@ Project template * The Vagrantfile now listens on port 8000 * Removed ``LOGIN_URL`` and ``LOGIN_REDIRECT_URL`` settings (as Wagtail no longer requires these) + * Removed example Celery configuration from ``production.py`` and ``requirements.txt`` Search @@ -123,6 +125,17 @@ It is no longer necessary to pass the base model as a parameter, so this declara The old format is now deprecated; all existing ``InlinePanel`` declarations should be updated to the new format. +Celery no longer automatically used for sending notification emails +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously, Wagtail would try to use Celery whenever the ``djcelery`` module was +installed, even if Celery wasn't actually set up. This could cause a very hard +to track down problem where notification emails would not be sent so this +functionality has now been removed. + +If you would like to keep using Celery for sending notification emails, have a +look at: `django-celery-email `_ + You no longer need ``LOGIN_URL`` and ``LOGIN_REDIRECT_URL`` to point to Wagtail admin. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/wagtail/contrib/wagtailfrontendcache/signal_handlers.py b/wagtail/contrib/wagtailfrontendcache/signal_handlers.py index fe673825f..3e5def1c1 100644 --- a/wagtail/contrib/wagtailfrontendcache/signal_handlers.py +++ b/wagtail/contrib/wagtailfrontendcache/signal_handlers.py @@ -1,4 +1,4 @@ -from django.db import models +from django.apps import apps from wagtail.wagtailcore.signals import page_published, page_unpublished @@ -15,8 +15,8 @@ def page_unpublished_signal_handler(instance, **kwargs): def register_signal_handlers(): # Get list of models that are page types - Page = models.get_model('wagtailcore', 'Page') - indexed_models = [model for model in models.get_models() if issubclass(model, Page)] + Page = apps.get_model('wagtailcore', 'Page') + indexed_models = [model for model in apps.get_models() if issubclass(model, Page)] # Loop through list and register signal handlers for each one for model in indexed_models: diff --git a/wagtail/contrib/wagtailroutablepage/tests.py b/wagtail/contrib/wagtailroutablepage/tests.py index 1aa8f53d1..2226b5cb5 100644 --- a/wagtail/contrib/wagtailroutablepage/tests.py +++ b/wagtail/contrib/wagtailroutablepage/tests.py @@ -1,7 +1,7 @@ from django.test import TestCase, RequestFactory from wagtail.wagtailcore.models import Page, Site -from wagtail.tests.models import RoutablePageTest, routable_page_external_view +from wagtail.tests.routablepage.models import RoutablePageTest, routable_page_external_view from wagtail.contrib.wagtailroutablepage.templatetags.wagtailroutablepage_tags import routablepageurl diff --git a/wagtail/contrib/wagtailsitemaps/tests.py b/wagtail/contrib/wagtailsitemaps/tests.py index a2d0498d7..5fba8f57e 100644 --- a/wagtail/contrib/wagtailsitemaps/tests.py +++ b/wagtail/contrib/wagtailsitemaps/tests.py @@ -2,7 +2,7 @@ from django.test import TestCase from django.core.cache import cache from wagtail.wagtailcore.models import Page, PageViewRestriction, Site -from wagtail.tests.models import SimplePage, EventIndex +from wagtail.tests.testapp.models import SimplePage, EventIndex from .sitemap_generator import Sitemap diff --git a/wagtail/project_template/project_name/settings/production.py b/wagtail/project_template/project_name/settings/production.py index e9e9e1971..bf20125a9 100644 --- a/wagtail/project_template/project_name/settings/production.py +++ b/wagtail/project_template/project_name/settings/production.py @@ -13,19 +13,6 @@ TEMPLATE_DEBUG = False COMPRESS_OFFLINE = True -# Send notification emails as a background task using Celery, -# to prevent this from blocking web server threads -# (requires the django-celery package): -# http://celery.readthedocs.org/en/latest/configuration.html - -# import djcelery -# -# djcelery.setup_loader() -# -# CELERY_SEND_TASK_ERROR_EMAILS = True -# BROKER_URL = 'redis://' - - # Use Redis as the cache backend for extra performance # (requires the django-redis-cache package): # http://wagtail.readthedocs.org/en/latest/howto/performance.html#cache diff --git a/wagtail/project_template/requirements.txt b/wagtail/project_template/requirements.txt index cccb37a38..a2ad30e9b 100644 --- a/wagtail/project_template/requirements.txt +++ b/wagtail/project_template/requirements.txt @@ -8,4 +8,3 @@ wagtail==1.0b1 # Recommended components to improve performance in production: # django-redis==3.8.2 -# django-celery==3.1.10 diff --git a/wagtail/tests/migrations/__init__.py b/wagtail/tests/customuser/__init__.py similarity index 100% rename from wagtail/tests/migrations/__init__.py rename to wagtail/tests/customuser/__init__.py diff --git a/wagtail/tests/migrations/0001_initial.py b/wagtail/tests/customuser/migrations/0001_initial.py similarity index 100% rename from wagtail/tests/migrations/0001_initial.py rename to wagtail/tests/customuser/migrations/0001_initial.py diff --git a/wagtail/tests/customuser/migrations/0002_auto_20150330_0653.py b/wagtail/tests/customuser/migrations/0002_auto_20150330_0653.py new file mode 100644 index 000000000..30be9eec6 --- /dev/null +++ b/wagtail/tests/customuser/migrations/0002_auto_20150330_0653.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('customuser', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='groups', + field=models.ManyToManyField(to='auth.Group', verbose_name='groups', help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', related_name='user_set', blank=True, related_query_name='user'), + preserve_default=True, + ), + migrations.AlterField( + model_name='customuser', + name='user_permissions', + field=models.ManyToManyField(to='auth.Permission', verbose_name='user permissions', help_text='Specific permissions for this user.', related_name='user_set', blank=True, related_query_name='user'), + preserve_default=True, + ), + ] diff --git a/wagtail/tests/customuser/migrations/__init__.py b/wagtail/tests/customuser/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/tests/customuser/models.py b/wagtail/tests/customuser/models.py new file mode 100644 index 000000000..cd09fa6e3 --- /dev/null +++ b/wagtail/tests/customuser/models.py @@ -0,0 +1,47 @@ +from django.db import models + +from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager + + +class CustomUserManager(BaseUserManager): + def _create_user(self, username, email, password, + is_staff, is_superuser, **extra_fields): + """ + Creates and saves a User with the given username, email and password. + """ + if not username: + raise ValueError('The given username must be set') + email = self.normalize_email(email) + user = self.model(username=username, email=email, + is_staff=is_staff, is_active=True, + is_superuser=is_superuser, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_user(self, username, email=None, password=None, **extra_fields): + return self._create_user(username, email, password, False, False, + **extra_fields) + + def create_superuser(self, username, email, password, **extra_fields): + return self._create_user(username, email, password, True, True, + **extra_fields) + + +class CustomUser(AbstractBaseUser, PermissionsMixin): + username = models.CharField(max_length=100, unique=True) + email = models.EmailField(max_length=255, blank=True) + is_staff = models.BooleanField(default=True) + is_active = models.BooleanField(default=True) + first_name = models.CharField(max_length=50, blank=True) + last_name = models.CharField(max_length=50, blank=True) + + USERNAME_FIELD = 'username' + + objects = CustomUserManager() + + def get_full_name(self): + return self.first_name + ' ' + self.last_name + + def get_short_name(self): + return self.first_name diff --git a/wagtail/tests/demosite/__init__.py b/wagtail/tests/demosite/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/tests/demosite/fixtures/demosite.json b/wagtail/tests/demosite/fixtures/demosite.json new file mode 100644 index 000000000..4c730e064 --- /dev/null +++ b/wagtail/tests/demosite/fixtures/demosite.json @@ -0,0 +1,1150 @@ +[ +{ + "pk": 1, + "model": "wagtailcore.page", + "fields": { + "title": "Root", + "numchild": 1, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 1, + "search_description": "", + "content_type": [ + "wagtailcore", + "page" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001", + "url_path": "/", + "slug": "root" + } +}, +{ + "pk": 2, + "model": "wagtailcore.page", + "fields": { + "title": "Home page", + "numchild": 5, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 2, + "search_description": "", + "content_type": [ + "demosite", + "homepage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "00010002", + "url_path": "/home-page/", + "slug": "home-page" + } +}, +{ + "pk": 4, + "model": "wagtailcore.page", + "fields": { + "title": "Events index", + "numchild": 2, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 3, + "search_description": "", + "content_type": [ + "demosite", + "eventindexpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "000100020001", + "url_path": "/home-page/events-index/", + "slug": "events-index" + } +}, +{ + "pk": 5, + "model": "wagtailcore.page", + "fields": { + "title": "Blog index", + "numchild": 3, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 3, + "search_description": "", + "content_type": [ + "demosite", + "blogindexpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "000100020002", + "url_path": "/home-page/blog-index/", + "slug": "blog-index" + } +}, +{ + "pk": 6, + "model": "wagtailcore.page", + "fields": { + "title": "Standard index", + "numchild": 4, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 3, + "search_description": "", + "content_type": [ + "demosite", + "standardindexpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "000100020003", + "url_path": "/home-page/standard-index/", + "slug": "standard-index" + } +}, +{ + "pk": 8, + "model": "wagtailcore.page", + "fields": { + "title": "Event 1", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one", + "content_type": [ + "demosite", + "eventpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200010001", + "url_path": "/home-page/events-index/event-1/", + "slug": "event-1" + } +}, +{ + "pk": 9, + "model": "wagtailcore.page", + "fields": { + "title": "Event 2", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.", + "content_type": [ + "demosite", + "eventpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200010002", + "url_path": "/home-page/events-index/event-2/", + "slug": "event-2" + } +}, +{ + "pk": 10, + "model": "wagtailcore.page", + "fields": { + "title": "Standard page 1", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters.", + "content_type": [ + "demosite", + "standardpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200030001", + "url_path": "/home-page/standard-index/standard-page-1/", + "slug": "standard-page-1" + } +}, +{ + "pk": 12, + "model": "wagtailcore.page", + "fields": { + "title": "Contact page", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 3, + "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean...", + "content_type": [ + "demosite", + "contactpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "000100020005", + "url_path": "/home-page/contact-page/", + "slug": "contact-page" + } +}, +{ + "pk": 13, + "model": "wagtailcore.page", + "fields": { + "title": "James Joyce", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa", + "content_type": [ + "demosite", + "personpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200040001", + "url_path": "/home-page/people/james-joyce/", + "slug": "james-joyce" + } +}, +{ + "pk": 14, + "model": "wagtailcore.page", + "fields": { + "title": "David Mitchell", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.", + "content_type": [ + "demosite", + "personpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200040002", + "url_path": "/home-page/people/david-mitchell/", + "slug": "david-mitchell" + } +}, +{ + "pk": 15, + "model": "wagtailcore.page", + "fields": { + "title": "Standard page 2", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.", + "content_type": [ + "demosite", + "standardpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200030002", + "url_path": "/home-page/standard-index/standard-page-2/", + "slug": "standard-page-2" + } +}, +{ + "pk": 16, + "model": "wagtailcore.page", + "fields": { + "title": "Blog post", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.", + "content_type": [ + "demosite", + "blogentrypage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200020001", + "url_path": "/home-page/blog-index/blog-post/", + "slug": "blog-post" + } +}, +{ + "pk": 17, + "model": "wagtailcore.page", + "fields": { + "title": "Photo credits", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "", + "content_type": [ + "demosite", + "standardpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200030007", + "url_path": "/home-page/standard-index/photo-credits/", + "slug": "photo-credits" + } +}, +{ + "pk": 18, + "model": "wagtailcore.page", + "fields": { + "title": "Blog post again", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.", + "content_type": [ + "demosite", + "blogentrypage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200020002", + "url_path": "/home-page/blog-index/blog-post-again/", + "slug": "blog-post-again" + } +}, +{ + "pk": 19, + "model": "wagtailcore.page", + "fields": { + "title": "Another blog post", + "numchild": 0, + "show_in_menus": false, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.", + "content_type": [ + "demosite", + "blogentrypage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200020003", + "url_path": "/home-page/blog-index/another-blog-post/", + "slug": "another-blog-post" + } +}, +{ + "pk": 20, + "model": "wagtailcore.page", + "fields": { + "title": "People", + "numchild": 2, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 3, + "search_description": "", + "content_type": [ + "demosite", + "standardindexpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "000100020004", + "url_path": "/home-page/people/", + "slug": "people" + } +}, +{ + "pk": 21, + "model": "wagtailcore.page", + "fields": { + "title": "A deeper menu level", + "numchild": 2, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 4, + "search_description": "", + "content_type": [ + "demosite", + "standardindexpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "0001000200030008", + "url_path": "/home-page/standard-index/a-deeper-menu-level/", + "slug": "a-deeper-menu-level" + } +}, +{ + "pk": 22, + "model": "wagtailcore.page", + "fields": { + "title": "A grandchild page", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 5, + "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.", + "content_type": [ + "demosite", + "standardpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "00010002000300080001", + "url_path": "/home-page/standard-index/a-deeper-menu-level/a-grandchild-page/", + "slug": "a-grandchild-page" + } +}, +{ + "pk": 23, + "model": "wagtailcore.page", + "fields": { + "title": "Another grandchild page", + "numchild": 0, + "show_in_menus": true, + "live": true, + "seo_title": "", + "depth": 5, + "search_description": "The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.", + "content_type": [ + "demosite", + "standardpage" + ], + "has_unpublished_changes": false, + "owner": null, + "path": "00010002000300080002", + "url_path": "/home-page/standard-index/a-deeper-menu-level/another-grandchild-page/", + "slug": "another-grandchild-page" + } +}, +{ + "pk": 4, + "model": "wagtailimages.image", + "fields": { + "title": "Wagtail by mark Harkin", + "created_at": "2014-02-06T10:14:47.173Z", + "height": 427, + "width": 640, + "file": "original_images/wagtail_by_markyharky.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 5, + "model": "wagtailimages.image", + "fields": { + "title": "James Joyce", + "created_at": "2014-02-06T10:37:10.518Z", + "height": 392, + "width": 500, + "file": "original_images/James_Joyce_in_1915.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 6, + "model": "wagtailimages.image", + "fields": { + "title": "David Mitchell", + "created_at": "2014-02-06T10:42:46.536Z", + "height": 282, + "width": 360, + "file": "original_images/David_Mitchell_by_Kubik.JPG", + "uploaded_by_user": null + } +}, +{ + "pk": 7, + "model": "wagtailimages.image", + "fields": { + "title": "Wagtail by joe Buckingham", + "created_at": "2014-02-06T10:49:29.579Z", + "height": 397, + "width": 640, + "file": "original_images/wagtail_by_joe_buckingham.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 8, + "model": "wagtailimages.image", + "fields": { + "title": "Wagtail by fs-phil", + "created_at": "2014-02-06T10:54:29.963Z", + "height": 397, + "width": 640, + "file": "original_images/wagtail_by_fs-phil.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 9, + "model": "wagtailimages.image", + "fields": { + "title": "White wagtail by Koshy Koshy", + "created_at": "2014-02-06T10:57:05.536Z", + "height": 397, + "width": 640, + "file": "original_images/white_wagtail_by_Koshyk.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 10, + "model": "wagtailimages.image", + "fields": { + "title": "Pied wagtail by Marie Hale", + "created_at": "2014-02-06T11:05:12.370Z", + "height": 397, + "width": 640, + "file": "original_images/pied_wagtail_by_Marie_Hale.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 11, + "model": "wagtailimages.image", + "fields": { + "title": "Wagtail at Borovoye, Kazakhstan by Ken and Nyetta", + "created_at": "2014-02-06T11:08:09.355Z", + "height": 397, + "width": 640, + "file": "original_images/wagtail_at_Borovoye_Kazakhstan_by_Ken_and_Nyetta.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 12, + "model": "wagtailimages.image", + "fields": { + "title": "Wagtail sproing by Jim Bendon", + "created_at": "2014-02-06T11:10:01.185Z", + "height": 397, + "width": 640, + "file": "original_images/wagtail_sproing_by_Jim_Bendon.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 13, + "model": "wagtailimages.image", + "fields": { + "title": "Hopalong wagtail by Ruth Flickr", + "created_at": "2014-02-06T11:15:32.454Z", + "height": 397, + "width": 640, + "file": "original_images/hopalong_wagtail_by_Ruth_Flickr.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 14, + "model": "wagtailimages.image", + "fields": { + "title": "Wagtail collects insects by Margrit", + "created_at": "2014-02-06T11:21:13.596Z", + "height": 397, + "width": 640, + "file": "original_images/wagtail_collects_insects_by_Maggi_94.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 15, + "model": "wagtailimages.image", + "fields": { + "title": "Grey wagtail by Lip Kee", + "created_at": "2014-02-06T11:23:36.409Z", + "height": 397, + "width": 640, + "file": "original_images/grey_wagtail_by_lip_kee.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 5, + "model": "demosite.blogindexpage", + "fields": { + "intro": "

Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.

" + } +}, +{ + "pk": 1, + "model": "demosite.blogindexpagerelatedlink", + "fields": { + "link_page": 4, + "title": "Events index", + "link_external": "", + "sort_order": 0, + "link_document": null, + "page": 5 + } +}, +{ + "pk": 16, + "model": "demosite.blogentrypage", + "fields": { + "body": "

mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.

The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.


mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.

The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.

mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.

The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.

", + "date": "2013-12-02", + "feed_image": 7 + } +}, +{ + "pk": 18, + "model": "demosite.blogentrypage", + "fields": { + "body": "

Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.




At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.


mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.


The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.


Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.


Prehistoric wagtails known from fossils are Motacilla humata and Motacilla major.


See the species accounts for more on individual species' relationships.

", + "date": "2014-01-10", + "feed_image": 15 + } +}, +{ + "pk": 19, + "model": "demosite.blogentrypage", + "fields": { + "body": "

Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.


At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.


mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.


The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.


Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.


Prehistoric wagtails known from fossils are Motacilla humata and Motacilla major.


See the species accounts for more on individual species' relationships.

", + "date": "2014-02-01", + "feed_image": 14 + } +}, +{ + "pk": 1, + "model": "demosite.blogentrypagecarouselitem", + "fields": { + "link_page": null, + "embed_url": "", + "image": 9, + "link_external": "http://www.flickr.com/photos/kkoshy/", + "caption": "White wagtail by Koshy Koshy", + "sort_order": 0, + "link_document": null, + "page": 16 + } +}, +{ + "pk": 2, + "model": "demosite.blogentrypagecarouselitem", + "fields": { + "link_page": null, + "embed_url": "", + "image": 7, + "link_external": "http://www.flickr.com/photos/jim_bendon_1957/", + "caption": "Wagtail by Jim Bendon", + "sort_order": 1, + "link_document": null, + "page": 16 + } +}, +{ + "pk": 4, + "model": "demosite.blogentrypagetag", + "fields": { + "content_object": 19, + "tag": 5 + } +}, +{ + "pk": 5, + "model": "demosite.blogentrypagetag", + "fields": { + "content_object": 16, + "tag": 4 + } +}, +{ + "pk": 6, + "model": "demosite.blogentrypagetag", + "fields": { + "content_object": 16, + "tag": 5 + } +}, +{ + "pk": 7, + "model": "demosite.blogentrypagetag", + "fields": { + "content_object": 18, + "tag": 4 + } +}, +{ + "pk": 12, + "model": "demosite.contactpage", + "fields": { + "body": "

Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.

", + "city": "Birdland", + "post_code": "W1A 1AA", + "country": "Birdshire", + "telephone": "012345 123456", + "address_1": "21 Tweety Mansions", + "address_2": "3 Bird Lane", + "email": "foo@example.com", + "feed_image": 7 + } +}, +{ + "pk": 4, + "model": "demosite.eventindexpage", + "fields": { + "intro": "

Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.

" + } +}, +{ + "pk": 8, + "model": "demosite.eventpage", + "fields": { + "body": "

Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.

At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.

", + "feed_image": 8, + "date_from": "2018-02-28", + "time_from": "11:30:00", + "audience": "public", + "cost": "\u00a330 per person, \u00a310 concessions", + "location": "Royal Albert Hall", + "date_to": "2018-03-02", + "time_to": "19:00:00", + "signup_link": "http://www.eventbrite.com/" + } +}, +{ + "pk": 9, + "model": "demosite.eventpage", + "fields": { + "body": "

Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution

", + "feed_image": 10, + "date_from": "2018-04-26", + "time_from": null, + "audience": "private", + "cost": "\u00a350", + "location": "O2 Arena", + "date_to": null, + "time_to": null, + "signup_link": "" + } +}, +{ + "pk": 1, + "model": "demosite.eventpagespeaker", + "fields": { + "last_name": "Joyce", + "first_name": "James", + "link_page": null, + "image": 5, + "link_external": "", + "sort_order": 0, + "link_document": null, + "page": 8 + } +}, +{ + "pk": 2, + "model": "demosite.eventpagespeaker", + "fields": { + "last_name": "Mitchell", + "first_name": "David", + "link_page": null, + "image": 6, + "link_external": "", + "sort_order": 1, + "link_document": null, + "page": 8 + } +}, +{ + "pk": 2, + "model": "demosite.homepage", + "fields": { + "body": "

Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.

" + } +}, +{ + "pk": 1, + "model": "demosite.homepagecarouselitem", + "fields": { + "link_page": null, + "embed_url": "", + "image": 15, + "link_external": "http://www.flickr.com/photos/lipkee/", + "caption": "Grey wagtail by Lip Kee", + "sort_order": 0, + "link_document": null, + "page": 2 + } +}, +{ + "pk": 2, + "model": "demosite.homepagecarouselitem", + "fields": { + "link_page": null, + "embed_url": "", + "image": 12, + "link_external": "http://www.flickr.com/photos/jim_bendon_1957/", + "caption": "Wagtail sproing by Jim Bendon", + "sort_order": 1, + "link_document": null, + "page": 2 + } +}, +{ + "pk": 3, + "model": "demosite.homepagecarouselitem", + "fields": { + "link_page": null, + "embed_url": "", + "image": 11, + "link_external": "http://www.flickr.com/photos/kjfnjy/", + "caption": "Wagtail at Borovoye, Kazakhstan by Ken and Nyetta", + "sort_order": 2, + "link_document": null, + "page": 2 + } +}, +{ + "pk": 13, + "model": "demosite.personpage", + "fields": { + "feed_image": 5, + "city": "Birdland", + "first_name": "James", + "post_code": "W1A 1AA", + "country": "Birdshire", + "image": 5, + "telephone": "012345 123456", + "last_name": "Joyce", + "address_1": "21 Tweety Mansions", + "address_2": "3 Bird Lane", + "intro": "

The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean

", + "email": "foo@example.com", + "biography": "

Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.


At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.

" + } +}, +{ + "pk": 14, + "model": "demosite.personpage", + "fields": { + "feed_image": 6, + "city": "", + "first_name": "David", + "post_code": "W1A 1AA", + "country": "", + "image": 6, + "telephone": "", + "last_name": "Mitchell", + "address_1": "", + "address_2": "", + "intro": "

Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World.

", + "email": "foo@example.com", + "biography": "

Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.

" + } +}, +{ + "pk": 6, + "model": "demosite.standardindexpage", + "fields": { + "feed_image": null, + "intro": "

Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.

" + } +}, +{ + "pk": 20, + "model": "demosite.standardindexpage", + "fields": { + "feed_image": null, + "intro": "" + } +}, +{ + "pk": 21, + "model": "demosite.standardindexpage", + "fields": { + "feed_image": null, + "intro": "

A listing of pages at the next level down

" + } +}, +{ + "pk": 10, + "model": "demosite.standardpage", + "fields": { + "body": "

Wagtails are slender, often colourful, ground-feeding insectivores of open country in the Old World. They are ground nesters, laying up to six speckled eggs at a time. Among their most conspicuous behaviours is a near constant tail wagging, a trait that has given the birds their common name. In spite of the ubiquity of the behaviour and observations of it, the reasons for it are poorly understood. It has been suggested that it may flush up prey, or that it may signal submissiveness to other wagtails. Recent studies have suggested instead that it is a signal of vigilance that may aid to deter potential predators.


At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.


mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.


The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.

", + "feed_image": 12, + "intro": "

At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.

" + } +}, +{ + "pk": 15, + "model": "demosite.standardpage", + "fields": { + "body": "

Wagtails are great

At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours. However, these are not evolutionary lineages; change of belly colour and increase of melanin have occurred independently several times in the wagtails, and the colour patterns which actually indicate relationships are more subtle.


Wagtails are pretty

mtDNA cytochrome b and NADH dehydrogenase subunit 2 sequence data (Voelker, 2002) is of limited use: the suspicion that there is a superspecies of probably 3 white-bellied, black-throated wagtails is confirmed. Also, there is another superspecies in sub-Saharan Africa, three white-throated species with a black breast-band. The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.


The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.

Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.

", + "feed_image": 10, + "intro": "

The remaining five species are highly variable morphologically and their relationships with each other and with the two clades have not yet been satisfactorily explained.

" + } +}, +{ + "pk": 17, + "model": "demosite.standardpage", + "fields": { + "body": "

James Joyce,\u00a01915,\u00a0Cornell Joyce Collection\u00a0by C. Ruf - public domain in the United States

\n

David Mitchell (b. 1969), British writer,\u00a0Warsaw (Poland), April 7, 2006, \u00a0Mariusz Kubik, GDFL licence

Wagtail,\u00a0October 14, 2012\u00a0by Mark Harkin, \u00a0\u00a0Creative Commons

Wagtail, February 18th 2008, by Joe Buckingham, Creative Commons

Wagtail, August 4th 2009, by fs-phil, Creative Commons

White wagtail, February 5th 2012, by Koshy Koshy, Creative Commons

Pied wagtail, January 20th 2013, by Marie Hale, Creative Commons

Wagtail at Borovoye, Kazakhstan, June 16th 2012, by Ken and Nyetta,\u00a0Creative Commons

Wagtail sproing, April 29 2012, by Jim Bendon, Creative Commons

Hopalong wagtail, June 17th 2008, by Ruth Flickr, Creative Commons

Wagtail collects insects, June 10th 2010, by Margrit, Creative Commons

Grey wagtail, March 13th 2009, by Lip Kee, Creative Commons

", + "feed_image": 15, + "intro": "

The following photos have been used in the sample wagtailapi tests database

" + } +}, +{ + "pk": 22, + "model": "demosite.standardpage", + "fields": { + "body": "

The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.


Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.

", + "feed_image": null, + "intro": "

At first glance, the wagtails appear to be divided into a yellow-bellied group and a white-bellied one, or one where the upper head is black and another where it is usually gray, but may be olive, yellow, or other colours.

" + } +}, +{ + "pk": 23, + "model": "demosite.standardpage", + "fields": { + "body": "

The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.


Three species are poly- or paraphyletic in the present taxonomical arrangement and either subspecies need to be reassigned and/or species split up. The Blue-headed Wagtail (AKA Yellow Wagtail and many other names), especially, has always been a taxonomical nightmare with over a dozen currently accepted subspecies and many more invalid ones. The two remaining \"monochrome\" species, Mekong and African Pied Wagtail may be closely related, or a most striking example of convergent evolution.

", + "feed_image": null, + "intro": "

The origin of the genus appears to be in the general area of Eastern Siberia/Mongolia. Wagtails spread rapidly across Eurasia and dispersed to Africa in the Zanclean (Early Pliocene) where the sub-Saharan lineage was later isolated. The African Pied Wagtail (and possibly the Mekong Wagtail) diverged prior to the massive radiation of the white-bellied black-throated and most yellow-bellied forms, all of which took place during the late Piacenzian (early Late Pliocene), c. 3 mya.

" + } +}, +{ + "pk": 1, + "model": "demosite.standardpagecarouselitem", + "fields": { + "link_page": null, + "embed_url": "", + "image": 13, + "link_external": "http://www.flickr.com/photos/ruthhb/", + "caption": "Hopalong wagtail by Ruth Flickr", + "sort_order": 0, + "link_document": null, + "page": 10 + } +}, +{ + "pk": 2, + "model": "demosite.standardpagecarouselitem", + "fields": { + "link_page": null, + "embed_url": "", + "image": 15, + "link_external": "http://www.flickr.com/photos/lipkee/", + "caption": "Grey wagtail by Lip Kee", + "sort_order": 1, + "link_document": null, + "page": 10 + } +}, +{ + "pk": 1, + "model": "demosite.standardpagerelatedlink", + "fields": { + "link_page": 4, + "title": "Internal link to events", + "link_external": "", + "sort_order": 0, + "link_document": null, + "page": 10 + } +}, +{ + "pk": 2, + "model": "demosite.standardpagerelatedlink", + "fields": { + "link_page": null, + "title": "External link to google", + "link_external": "http://www.google.com/", + "sort_order": 1, + "link_document": null, + "page": 10 + } +}, +{ + "pk": 1, + "model": "taggit.tag", + "fields": { + "name": "writers", + "slug": "writers" + } +}, +{ + "pk": 2, + "model": "taggit.tag", + "fields": { + "name": "people", + "slug": "people" + } +}, +{ + "pk": 3, + "model": "taggit.tag", + "fields": { + "name": "person", + "slug": "person" + } +}, +{ + "pk": 4, + "model": "taggit.tag", + "fields": { + "name": "wagtail", + "slug": "wagtail" + } +}, +{ + "pk": 5, + "model": "taggit.tag", + "fields": { + "name": "bird", + "slug": "bird" + } +}, +{ + "pk": 6, + "model": "taggit.tag", + "fields": { + "name": "writer", + "slug": "writer" + } +}, +{ + "pk": 1, + "model": "wagtaildocs.document", + "fields": { + "title": "Wagtail by mark Harkin", + "created_at": "2014-02-06T10:14:47.173Z", + "file": "original_images/wagtail_by_markyharky.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 2, + "model": "wagtaildocs.document", + "fields": { + "title": "James Joyce", + "created_at": "2014-02-06T10:37:10.518Z", + "file": "original_images/James_Joyce_in_1915.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 3, + "model": "wagtaildocs.document", + "fields": { + "title": "David Mitchell", + "created_at": "2014-02-06T10:42:46.536Z", + "file": "original_images/David_Mitchell_by_Kubik.JPG", + "uploaded_by_user": null + } +}, +{ + "pk": 4, + "model": "wagtaildocs.document", + "fields": { + "title": "Wagtail by joe Buckingham", + "created_at": "2014-02-06T10:49:29.579Z", + "file": "original_images/wagtail_by_joe_buckingham.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 5, + "model": "wagtaildocs.document", + "fields": { + "title": "Wagtail by fs-phil", + "created_at": "2014-02-06T10:54:29.963Z", + "file": "original_images/wagtail_by_fs-phil.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 6, + "model": "wagtaildocs.document", + "fields": { + "title": "White wagtail by Koshy Koshy", + "created_at": "2014-02-06T10:57:05.536Z", + "file": "original_images/white_wagtail_by_Koshyk.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 7, + "model": "wagtaildocs.document", + "fields": { + "title": "Pied wagtail by Marie Hale", + "created_at": "2014-02-06T11:05:12.370Z", + "file": "original_images/pied_wagtail_by_Marie_Hale.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 8, + "model": "wagtaildocs.document", + "fields": { + "title": "Wagtail at Borovoye, Kazakhstan by Ken and Nyetta", + "created_at": "2014-02-06T11:08:09.355Z", + "file": "original_images/wagtail_at_Borovoye_Kazakhstan_by_Ken_and_Nyetta.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 9, + "model": "wagtaildocs.document", + "fields": { + "title": "Wagtail sproing by Jim Bendon", + "created_at": "2014-02-06T11:10:01.185Z", + "file": "original_images/wagtail_sproing_by_Jim_Bendon.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 10, + "model": "wagtaildocs.document", + "fields": { + "title": "Hopalong wagtail by Ruth Flickr", + "created_at": "2014-02-06T11:15:32.454Z", + "file": "original_images/hopalong_wagtail_by_Ruth_Flickr.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 11, + "model": "wagtaildocs.document", + "fields": { + "title": "Wagtail collects insects by Margrit", + "created_at": "2014-02-06T11:21:13.596Z", + "file": "original_images/wagtail_collects_insects_by_Maggi_94.jpg", + "uploaded_by_user": null + } +}, +{ + "pk": 12, + "model": "wagtaildocs.document", + "fields": { + "title": "Grey wagtail by Lip Kee", + "created_at": "2014-02-06T11:23:36.409Z", + "file": "original_images/grey_wagtail_by_lip_kee.jpg", + "uploaded_by_user": null + } +} +] diff --git a/wagtail/tests/demosite/migrations/0001_initial.py b/wagtail/tests/demosite/migrations/0001_initial.py new file mode 100644 index 000000000..a7eb7c757 --- /dev/null +++ b/wagtail/tests/demosite/migrations/0001_initial.py @@ -0,0 +1,513 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.db.models.deletion +import modelcluster.tags +import wagtail.wagtailcore.fields +import modelcluster.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailimages', '0005_make_filter_spec_unique'), + ('taggit', '0001_initial'), + ('wagtaildocs', '0002_initial_data'), + ('wagtailcore', '0013_update_golive_expire_help_text'), + ] + + operations = [ + migrations.CreateModel( + name='BlogEntryPage', + fields=[ + ('page_ptr', models.OneToOneField(to='wagtailcore.Page', serialize=False, parent_link=True, related_name='+', primary_key=True)), + ('body', wagtail.wagtailcore.fields.RichTextField()), + ('date', models.DateField(verbose_name='Post date')), + ('feed_image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='BlogEntryPageCarouselItem', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('embed_url', models.URLField(blank=True, verbose_name='Embed URL')), + ('caption', models.CharField(blank=True, max_length=255)), + ('image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='BlogEntryPageRelatedLink', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('title', models.CharField(help_text='Link title', max_length=255)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='BlogEntryPageTag', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('content_object', modelcluster.fields.ParentalKey(to='demosite.BlogEntryPage', related_name='tagged_items')), + ('tag', models.ForeignKey(to='taggit.Tag', related_name='demosite_blogentrypagetag_items')), + ], + options={ + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='BlogIndexPage', + fields=[ + ('page_ptr', models.OneToOneField(to='wagtailcore.Page', serialize=False, parent_link=True, related_name='+', primary_key=True)), + ('intro', wagtail.wagtailcore.fields.RichTextField(blank=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='BlogIndexPageRelatedLink', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('title', models.CharField(help_text='Link title', max_length=255)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ContactPage', + fields=[ + ('telephone', models.CharField(blank=True, max_length=20)), + ('email', models.EmailField(blank=True, max_length=75)), + ('address_1', models.CharField(blank=True, max_length=255)), + ('address_2', models.CharField(blank=True, max_length=255)), + ('city', models.CharField(blank=True, max_length=255)), + ('country', models.CharField(blank=True, max_length=255)), + ('post_code', models.CharField(blank=True, max_length=10)), + ('page_ptr', models.OneToOneField(to='wagtailcore.Page', serialize=False, parent_link=True, related_name='+', primary_key=True)), + ('body', wagtail.wagtailcore.fields.RichTextField(blank=True)), + ('feed_image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page', models.Model), + ), + migrations.CreateModel( + name='EventIndexPage', + fields=[ + ('page_ptr', models.OneToOneField(to='wagtailcore.Page', serialize=False, parent_link=True, related_name='+', primary_key=True)), + ('intro', wagtail.wagtailcore.fields.RichTextField(blank=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='EventIndexPageRelatedLink', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('title', models.CharField(help_text='Link title', max_length=255)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='EventPage', + fields=[ + ('page_ptr', models.OneToOneField(to='wagtailcore.Page', serialize=False, parent_link=True, related_name='+', primary_key=True)), + ('date_from', models.DateField(verbose_name='Start date')), + ('date_to', models.DateField(help_text='Not required if event is on a single day', null=True, verbose_name='End date', blank=True)), + ('time_from', models.TimeField(null=True, verbose_name='Start time', blank=True)), + ('time_to', models.TimeField(null=True, verbose_name='End time', blank=True)), + ('audience', models.CharField(choices=[('public', 'Public'), ('private', 'Private')], max_length=255)), + ('location', models.CharField(max_length=255)), + ('body', wagtail.wagtailcore.fields.RichTextField(blank=True)), + ('cost', models.CharField(max_length=255)), + ('signup_link', models.URLField(blank=True)), + ('feed_image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='EventPageCarouselItem', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('embed_url', models.URLField(blank=True, verbose_name='Embed URL')), + ('caption', models.CharField(blank=True, max_length=255)), + ('image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='EventPageRelatedLink', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('title', models.CharField(help_text='Link title', max_length=255)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='EventPageSpeaker', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('first_name', models.CharField(blank=True, verbose_name='Name', max_length=255)), + ('last_name', models.CharField(blank=True, verbose_name='Surname', max_length=255)), + ('image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='HomePage', + fields=[ + ('page_ptr', models.OneToOneField(to='wagtailcore.Page', serialize=False, parent_link=True, related_name='+', primary_key=True)), + ('body', wagtail.wagtailcore.fields.RichTextField(blank=True)), + ], + options={ + 'verbose_name': 'Homepage', + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='HomePageCarouselItem', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('embed_url', models.URLField(blank=True, verbose_name='Embed URL')), + ('caption', models.CharField(blank=True, max_length=255)), + ('image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='HomePageRelatedLink', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('title', models.CharField(help_text='Link title', max_length=255)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='PersonPage', + fields=[ + ('telephone', models.CharField(blank=True, max_length=20)), + ('email', models.EmailField(blank=True, max_length=75)), + ('address_1', models.CharField(blank=True, max_length=255)), + ('address_2', models.CharField(blank=True, max_length=255)), + ('city', models.CharField(blank=True, max_length=255)), + ('country', models.CharField(blank=True, max_length=255)), + ('post_code', models.CharField(blank=True, max_length=10)), + ('page_ptr', models.OneToOneField(to='wagtailcore.Page', serialize=False, parent_link=True, related_name='+', primary_key=True)), + ('first_name', models.CharField(max_length=255)), + ('last_name', models.CharField(max_length=255)), + ('intro', wagtail.wagtailcore.fields.RichTextField(blank=True)), + ('biography', wagtail.wagtailcore.fields.RichTextField(blank=True)), + ('feed_image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ('image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page', models.Model), + ), + migrations.CreateModel( + name='PersonPageRelatedLink', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('title', models.CharField(help_text='Link title', max_length=255)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='StandardIndexPage', + fields=[ + ('page_ptr', models.OneToOneField(to='wagtailcore.Page', serialize=False, parent_link=True, related_name='+', primary_key=True)), + ('intro', wagtail.wagtailcore.fields.RichTextField(blank=True)), + ('feed_image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='StandardIndexPageRelatedLink', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('title', models.CharField(help_text='Link title', max_length=255)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='StandardPage', + fields=[ + ('page_ptr', models.OneToOneField(to='wagtailcore.Page', serialize=False, parent_link=True, related_name='+', primary_key=True)), + ('intro', wagtail.wagtailcore.fields.RichTextField(blank=True)), + ('body', wagtail.wagtailcore.fields.RichTextField(blank=True)), + ('feed_image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='StandardPageCarouselItem', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('embed_url', models.URLField(blank=True, verbose_name='Embed URL')), + ('caption', models.CharField(blank=True, max_length=255)), + ('image', models.ForeignKey(to='wagtailimages.Image', blank=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', null=True)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ('link_page', models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True)), + ('page', modelcluster.fields.ParentalKey(to='demosite.StandardPage', related_name='carousel_items')), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='StandardPageRelatedLink', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), + ('sort_order', models.IntegerField(null=True, editable=False, blank=True)), + ('link_external', models.URLField(blank=True, verbose_name='External link')), + ('title', models.CharField(help_text='Link title', max_length=255)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', blank=True, related_name='+', null=True)), + ('link_page', models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True)), + ('page', modelcluster.fields.ParentalKey(to='demosite.StandardPage', related_name='related_links')), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='standardindexpagerelatedlink', + name='link_page', + field=models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='standardindexpagerelatedlink', + name='page', + field=modelcluster.fields.ParentalKey(to='demosite.StandardIndexPage', related_name='related_links'), + preserve_default=True, + ), + migrations.AddField( + model_name='personpagerelatedlink', + name='link_page', + field=models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='personpagerelatedlink', + name='page', + field=modelcluster.fields.ParentalKey(to='demosite.PersonPage', related_name='related_links'), + preserve_default=True, + ), + migrations.AddField( + model_name='homepagerelatedlink', + name='link_page', + field=models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='homepagerelatedlink', + name='page', + field=modelcluster.fields.ParentalKey(to='demosite.HomePage', related_name='related_links'), + preserve_default=True, + ), + migrations.AddField( + model_name='homepagecarouselitem', + name='link_page', + field=models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='homepagecarouselitem', + name='page', + field=modelcluster.fields.ParentalKey(to='demosite.HomePage', related_name='carousel_items'), + preserve_default=True, + ), + migrations.AddField( + model_name='eventpagespeaker', + name='link_page', + field=models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='eventpagespeaker', + name='page', + field=modelcluster.fields.ParentalKey(to='demosite.EventPage', related_name='speakers'), + preserve_default=True, + ), + migrations.AddField( + model_name='eventpagerelatedlink', + name='link_page', + field=models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='eventpagerelatedlink', + name='page', + field=modelcluster.fields.ParentalKey(to='demosite.EventPage', related_name='related_links'), + preserve_default=True, + ), + migrations.AddField( + model_name='eventpagecarouselitem', + name='link_page', + field=models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='eventpagecarouselitem', + name='page', + field=modelcluster.fields.ParentalKey(to='demosite.EventPage', related_name='carousel_items'), + preserve_default=True, + ), + migrations.AddField( + model_name='eventindexpagerelatedlink', + name='link_page', + field=models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='eventindexpagerelatedlink', + name='page', + field=modelcluster.fields.ParentalKey(to='demosite.EventIndexPage', related_name='related_links'), + preserve_default=True, + ), + migrations.AddField( + model_name='blogindexpagerelatedlink', + name='link_page', + field=models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='blogindexpagerelatedlink', + name='page', + field=modelcluster.fields.ParentalKey(to='demosite.BlogIndexPage', related_name='related_links'), + preserve_default=True, + ), + migrations.AddField( + model_name='blogentrypagerelatedlink', + name='link_page', + field=models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='blogentrypagerelatedlink', + name='page', + field=modelcluster.fields.ParentalKey(to='demosite.BlogEntryPage', related_name='related_links'), + preserve_default=True, + ), + migrations.AddField( + model_name='blogentrypagecarouselitem', + name='link_page', + field=models.ForeignKey(to='wagtailcore.Page', blank=True, related_name='+', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='blogentrypagecarouselitem', + name='page', + field=modelcluster.fields.ParentalKey(to='demosite.BlogEntryPage', related_name='carousel_items'), + preserve_default=True, + ), + migrations.AddField( + model_name='blogentrypage', + name='tags', + field=modelcluster.tags.ClusterTaggableManager(help_text='A comma-separated list of tags.', through='demosite.BlogEntryPageTag', blank=True, verbose_name='Tags', to='taggit.Tag'), + preserve_default=True, + ), + ] diff --git a/wagtail/tests/demosite/migrations/__init__.py b/wagtail/tests/demosite/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/tests/demosite/models.py b/wagtail/tests/demosite/models.py new file mode 100644 index 000000000..724e61600 --- /dev/null +++ b/wagtail/tests/demosite/models.py @@ -0,0 +1,594 @@ +from django.db import models +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger + +from modelcluster.fields import ParentalKey +from modelcluster.tags import ClusterTaggableManager +from taggit.models import Tag, TaggedItemBase + +from wagtail.wagtailcore.models import Page, Orderable +from wagtail.wagtailcore.fields import RichTextField +from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, \ + InlinePanel, PageChooserPanel +from wagtail.wagtailimages.edit_handlers import ImageChooserPanel +from wagtail.wagtaildocs.edit_handlers import DocumentChooserPanel +from wagtail.wagtailsnippets.models import register_snippet +from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField +from wagtail.wagtailsearch import index + + +# ABSTRACT MODELS +# ============================= + +class AbstractLinkFields(models.Model): + link_external = models.URLField("External link", blank=True) + link_page = models.ForeignKey( + 'wagtailcore.Page', + null=True, + blank=True, + related_name='+' + ) + link_document = models.ForeignKey( + 'wagtaildocs.Document', + null=True, + blank=True, + related_name='+' + ) + + @property + def link(self): + if self.link_page: + return self.link_page.url + elif self.link_document: + return self.link_document.url + else: + return self.link_external + + api_fields = ('link', ) + + panels = [ + FieldPanel('link_external'), + PageChooserPanel('link_page'), + DocumentChooserPanel('link_document'), + ] + + class Meta: + abstract = True + + +class AbstractRelatedLink(AbstractLinkFields): + title = models.CharField(max_length=255, help_text="Link title") + + api_fields = ('title', ) + AbstractLinkFields.api_fields + + panels = [ + FieldPanel('title'), + MultiFieldPanel(AbstractLinkFields.panels, "Link"), + ] + + class Meta: + abstract = True + + +class AbstractCarouselItem(AbstractLinkFields): + image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + embed_url = models.URLField("Embed URL", blank=True) + caption = models.CharField(max_length=255, blank=True) + + api_fields = ( + 'image', + 'embed_url', + 'caption', + ) + AbstractLinkFields.api_fields + + panels = [ + ImageChooserPanel('image'), + FieldPanel('embed_url'), + FieldPanel('caption'), + MultiFieldPanel(AbstractLinkFields.panels, "Link"), + ] + + class Meta: + abstract = True + + +class ContactFieldsMixin(models.Model): + telephone = models.CharField(max_length=20, blank=True) + email = models.EmailField(blank=True) + address_1 = models.CharField(max_length=255, blank=True) + address_2 = models.CharField(max_length=255, blank=True) + city = models.CharField(max_length=255, blank=True) + country = models.CharField(max_length=255, blank=True) + post_code = models.CharField(max_length=10, blank=True) + + api_fields = ( + 'telephone', + 'email', + 'address_1', + 'address_2', + 'city', + 'country', + 'post_code', + ) + + panels = [ + FieldPanel('telephone'), + FieldPanel('email'), + FieldPanel('address_1'), + FieldPanel('address_2'), + FieldPanel('city'), + FieldPanel('country'), + FieldPanel('post_code'), + ] + + class Meta: + abstract = True + + +# PAGE MODELS +# ============================= + +# Home page + +class HomePage(Page): + page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+') + body = RichTextField(blank=True) + + api_fields = ( + 'body', + 'carousel_items', + 'related_links', + ) + + search_fields = Page.search_fields + ( + index.SearchField('body'), + ) + + class Meta: + verbose_name = "Homepage" + + +class HomePageCarouselItem(Orderable, AbstractCarouselItem): + page = ParentalKey('HomePage', related_name='carousel_items') + +class HomePageRelatedLink(Orderable, AbstractRelatedLink): + page = ParentalKey('HomePage', related_name='related_links') + +HomePage.content_panels = Page.content_panels + [ + FieldPanel('body', classname="full"), + + InlinePanel(HomePage, 'carousel_items', label="Carousel items"), + InlinePanel(HomePage, 'related_links', label="Related links"), +] + + +# Standard pages + +class StandardPage(Page): + page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+') + intro = RichTextField(blank=True) + body = RichTextField(blank=True) + feed_image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + api_fields = ( + 'intro', + 'body', + 'feed_image', + 'carousel_items', + 'related_links', + ) + + search_fields = Page.search_fields + ( + index.SearchField('intro'), + index.SearchField('body'), + ) + +class StandardPageCarouselItem(Orderable, AbstractCarouselItem): + page = ParentalKey('StandardPage', related_name='carousel_items') + +class StandardPageRelatedLink(Orderable, AbstractRelatedLink): + page = ParentalKey('StandardPage', related_name='related_links') + +StandardPage.content_panels = Page.content_panels + [ + FieldPanel('intro', classname="full"), + InlinePanel(StandardPage, 'carousel_items', label="Carousel items"), + FieldPanel('body', classname="full"), + InlinePanel(StandardPage, 'related_links', label="Related links"), +] + +StandardPage.promote_panels = [ + MultiFieldPanel(Page.promote_panels, "Common page configuration"), + ImageChooserPanel('feed_image'), +] + + +class StandardIndexPage(Page): + page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+') + intro = RichTextField(blank=True) + feed_image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + api_fields = ( + 'intro', + 'feed_image', + 'related_links', + ) + + search_fields = Page.search_fields + ( + index.SearchField('intro'), + ) + +class StandardIndexPageRelatedLink(Orderable, AbstractRelatedLink): + page = ParentalKey('StandardIndexPage', related_name='related_links') + +StandardIndexPage.content_panels = Page.content_panels + [ + FieldPanel('intro', classname="full"), + InlinePanel(StandardIndexPage, 'related_links', label="Related links"), +] + +StandardIndexPage.promote_panels = [ + MultiFieldPanel(Page.promote_panels, "Common page configuration"), + ImageChooserPanel('feed_image'), +] + + +# Blog pages + +class BlogEntryPage(Page): + page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+') + body = RichTextField() + tags = ClusterTaggableManager(through='BlogEntryPageTag', blank=True) + date = models.DateField("Post date") + feed_image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + api_fields = ( + 'body', + 'tags', + 'date', + 'feed_image', + 'carousel_items', + 'related_links', + ) + + search_fields = Page.search_fields + ( + index.SearchField('body'), + ) + + def get_blog_index(self): + # Find closest ancestor which is a blog index + return BlogIndexPage.ancestor_of(self).last() + +class BlogEntryPageCarouselItem(Orderable, AbstractCarouselItem): + page = ParentalKey('BlogEntryPage', related_name='carousel_items') + +class BlogEntryPageRelatedLink(Orderable, AbstractRelatedLink): + page = ParentalKey('BlogEntryPage', related_name='related_links') + +class BlogEntryPageTag(TaggedItemBase): + content_object = ParentalKey('BlogEntryPage', related_name='tagged_items') + +BlogEntryPage.content_panels = Page.content_panels + [ + FieldPanel('date'), + FieldPanel('body', classname="full"), + InlinePanel(BlogEntryPage, 'carousel_items', label="Carousel items"), + InlinePanel(BlogEntryPage, 'related_links', label="Related links"), +] + +BlogEntryPage.promote_panels = [ + MultiFieldPanel(Page.promote_panels, "Common page configuration"), + ImageChooserPanel('feed_image'), + FieldPanel('tags'), +] + + +class BlogIndexPage(Page): + page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+') + intro = RichTextField(blank=True) + + api_fields = ( + 'intro', + 'related_links', + ) + + search_fields = Page.search_fields + ( + index.SearchField('intro'), + ) + + def get_blog_entries(self): + # Get list of live blog pages that are descendants of this page + entries = BlogEntryPage.objects.descendant_of(self).live() + + # Order by most recent date first + entries = entries.order_by('-date') + + return entries + + def get_context(self, request): + # Get blog entries + entries = self.get_blog_entries() + + # Filter by tag + tag = request.GET.get('tag') + if tag: + entries = entries.filter(tags__name=tag) + + # Pagination + page = request.GET.get('page') + paginator = Paginator(entries, 10) # Show 10 entries per page + try: + entries = paginator.page(page) + except PageNotAnInteger: + entries = paginator.page(1) + except EmptyPage: + entries = paginator.page(paginator.num_pages) + + # Update template context + context = super(BlogIndexPage, self).get_context(request) + context['entries'] = entries + return context + +class BlogIndexPageRelatedLink(Orderable, AbstractRelatedLink): + page = ParentalKey('BlogIndexPage', related_name='related_links') + +BlogIndexPage.content_panels = Page.content_panels + [ + FieldPanel('intro', classname="full"), + InlinePanel(BlogIndexPage, 'related_links', label="Related links"), +] + + +# Events pages + +class EventPage(Page): + page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+') + AUDIENCE_CHOICES = ( + ('public', "Public"), + ('private', "Private"), + ) + + date_from = models.DateField("Start date") + date_to = models.DateField( + "End date", + null=True, + blank=True, + help_text="Not required if event is on a single day" + ) + time_from = models.TimeField("Start time", null=True, blank=True) + time_to = models.TimeField("End time", null=True, blank=True) + audience = models.CharField(max_length=255, choices=AUDIENCE_CHOICES) + location = models.CharField(max_length=255) + body = RichTextField(blank=True) + cost = models.CharField(max_length=255) + signup_link = models.URLField(blank=True) + feed_image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + api_fields = ( + 'date_from', + 'date_to', + 'time_from', + 'time_to', + 'audience', + 'location', + 'body', + 'cost', + 'signup_link', + 'feed_image', + 'carousel_items', + 'related_links', + 'speakers', + ) + + search_fields = Page.search_fields + ( + index.SearchField('get_audience_display'), + index.SearchField('location'), + index.SearchField('body'), + ) + + def get_event_index(self): + # Find closest ancestor which is an event index + return EventIndexPage.objects.ancester_of(self).last() + +class EventPageCarouselItem(Orderable, AbstractCarouselItem): + page = ParentalKey('EventPage', related_name='carousel_items') + +class EventPageRelatedLink(Orderable, AbstractRelatedLink): + page = ParentalKey('EventPage', related_name='related_links') + +class EventPageSpeaker(Orderable, AbstractLinkFields): + page = ParentalKey('EventPage', related_name='speakers') + first_name = models.CharField("Name", max_length=255, blank=True) + last_name = models.CharField("Surname", max_length=255, blank=True) + image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + api_fields = ( + 'first_name', + 'last_name', + 'image', + ) + + panels = [ + FieldPanel('first_name'), + FieldPanel('last_name'), + ImageChooserPanel('image'), + MultiFieldPanel(AbstractLinkFields.panels, "Link"), + ] + +EventPage.content_panels = Page.content_panels + [ + FieldPanel('date_from'), + FieldPanel('date_to'), + FieldPanel('time_from'), + FieldPanel('time_to'), + FieldPanel('location'), + FieldPanel('audience'), + FieldPanel('cost'), + FieldPanel('signup_link'), + InlinePanel(EventPage, 'carousel_items', label="Carousel items"), + FieldPanel('body', classname="full"), + InlinePanel(EventPage, 'speakers', label="Speakers"), + InlinePanel(EventPage, 'related_links', label="Related links"), +] + +EventPage.promote_panels = [ + MultiFieldPanel(Page.promote_panels, "Common page configuration"), + ImageChooserPanel('feed_image'), +] + + +class EventIndexPage(Page): + page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+') + intro = RichTextField(blank=True) + + api_fields = ( + 'intro', + 'related_links', + ) + + search_fields = Page.search_fields + ( + index.SearchField('intro'), + ) + + def get_events(self): + # Get list of live event pages that are descendants of this page + events = EventPage.objects.descendant_of(self).live() + + # Filter events list to get ones that are either + # running now or start in the future + events = events.filter(date_from__gte=date.today()) + + # Order by date + events = events.order_by('date_from') + + return events + +class EventIndexPageRelatedLink(Orderable, AbstractRelatedLink): + page = ParentalKey('EventIndexPage', related_name='related_links') + +EventIndexPage.content_panels = Page.content_panels + [ + FieldPanel('intro', classname="full"), + InlinePanel(EventIndexPage, 'related_links', label="Related links"), +] + + +# Person page + +class PersonPage(Page, ContactFieldsMixin): + page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+') + first_name = models.CharField(max_length=255) + last_name = models.CharField(max_length=255) + intro = RichTextField(blank=True) + biography = RichTextField(blank=True) + image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + feed_image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + api_fields = ( + 'first_name', + 'last_name', + 'intro', + 'biography', + 'image', + 'feed_image', + 'related_links', + ) + ContactFieldsMixin.api_fields + + search_fields = Page.search_fields + ( + index.SearchField('first_name'), + index.SearchField('last_name'), + index.SearchField('intro'), + index.SearchField('biography'), + ) + +class PersonPageRelatedLink(Orderable, AbstractRelatedLink): + page = ParentalKey('PersonPage', related_name='related_links') + +PersonPage.content_panels = Page.content_panels + [ + FieldPanel('first_name'), + FieldPanel('last_name'), + FieldPanel('intro', classname="full"), + FieldPanel('biography', classname="full"), + ImageChooserPanel('image'), + MultiFieldPanel(ContactFieldsMixin.panels, "Contact"), + InlinePanel(PersonPage, 'related_links', label="Related links"), +] + +PersonPage.promote_panels = [ + MultiFieldPanel(Page.promote_panels, "Common page configuration"), + ImageChooserPanel('feed_image'), +] + + +# Contact page + +class ContactPage(Page, ContactFieldsMixin): + page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+') + body = RichTextField(blank=True) + feed_image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + api_fields = ( + 'body', + 'feed_image', + ) + ContactFieldsMixin.api_fields + + search_fields = Page.search_fields + ( + index.SearchField('body'), + ) + +ContactPage.content_panels = Page.content_panels + [ + FieldPanel('body', classname="full"), + MultiFieldPanel(ContactFieldsMixin.panels, "Contact"), +] + +ContactPage.promote_panels = [ + MultiFieldPanel(Page.promote_panels, "Common page configuration"), + ImageChooserPanel('feed_image'), +] diff --git a/wagtail/tests/migrations/0003_auto_20140905_0634.py b/wagtail/tests/migrations/0003_auto_20140905_0634.py deleted file mode 100644 index c1f69f529..000000000 --- a/wagtail/tests/migrations/0003_auto_20140905_0634.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tests', '0002_auto_20140827_0908'), - ] - - operations = [ - migrations.AddField( - model_name='advertplacement', - name='colour', - field=models.CharField(default='blue', max_length=255), - preserve_default=False, - ), - ] diff --git a/wagtail/tests/migrations/0004_auto_20141008_0420.py b/wagtail/tests/migrations/0004_auto_20141008_0420.py deleted file mode 100644 index d5002a776..000000000 --- a/wagtail/tests/migrations/0004_auto_20141008_0420.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tests', '0003_auto_20140905_0634'), - ] - - operations = [ - migrations.DeleteModel( - name='SearchTestOldConfig', - ), - migrations.DeleteModel( - name='SearchTestOldConfigList', - ), - ] diff --git a/wagtail/tests/migrations/0005_auto_20141008_0122.py b/wagtail/tests/migrations/0005_auto_20141008_0122.py deleted file mode 100644 index 801e1f5df..000000000 --- a/wagtail/tests/migrations/0005_auto_20141008_0122.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('wagtailcore', '0002_initial_data'), - ('tests', '0004_auto_20141008_0420'), - ] - - operations = [ - migrations.CreateModel( - name='PageChooserModel', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('page', models.ForeignKey(help_text=b'help text', to='wagtailcore.Page')), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='SnippetChooserModel', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('advert', models.ForeignKey(help_text=b'help text', to='tests.Advert')), - ], - options={ - }, - bases=(models.Model,), - ), - ] diff --git a/wagtail/tests/migrations/0005_auto_20141113_0642.py b/wagtail/tests/migrations/0005_auto_20141113_0642.py deleted file mode 100644 index 37f5744c8..000000000 --- a/wagtail/tests/migrations/0005_auto_20141113_0642.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('wagtailcore', '0002_initial_data'), - ('tests', '0004_auto_20141008_0420'), - ] - - operations = [ - migrations.AlterField( - model_name='formfield', - name='choices', - field=models.CharField(help_text='Comma separated list of choices. Only applicable in checkboxes, radio and dropdown.', max_length=512, blank=True), - preserve_default=True, - ), - migrations.AlterField( - model_name='formfield', - name='default_value', - field=models.CharField(help_text='Default value. Comma separated values supported for checkboxes.', max_length=255, blank=True), - preserve_default=True, - ), - ] diff --git a/wagtail/tests/migrations/0006_merge.py b/wagtail/tests/migrations/0006_merge.py deleted file mode 100644 index 7489198a7..000000000 --- a/wagtail/tests/migrations/0006_merge.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tests', '0005_auto_20141113_0642'), - ('tests', '0005_auto_20141008_0122'), - ] - - operations = [ - ] diff --git a/wagtail/tests/migrations/0007_auto_20141118_0925.py b/wagtail/tests/migrations/0007_auto_20141118_0925.py deleted file mode 100644 index be6775130..000000000 --- a/wagtail/tests/migrations/0007_auto_20141118_0925.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tests', '0006_merge'), - ] - - operations = [ - migrations.AlterField( - model_name='pagechoosermodel', - name='page', - field=models.ForeignKey(to='wagtailcore.Page', help_text='help text'), - preserve_default=True, - ), - migrations.AlterField( - model_name='snippetchoosermodel', - name='advert', - field=models.ForeignKey(to='tests.Advert', help_text='help text'), - preserve_default=True, - ), - ] diff --git a/wagtail/tests/migrations/0008_registerdecorator.py b/wagtail/tests/migrations/0008_registerdecorator.py deleted file mode 100644 index c82debdf8..000000000 --- a/wagtail/tests/migrations/0008_registerdecorator.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tests', '0007_auto_20141118_0925'), - ] - - operations = [ - migrations.CreateModel( - name='RegisterDecorator', - fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='RegisterFunction', - fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), - ], - options={ - }, - bases=(models.Model,), - ), - ] diff --git a/wagtail/tests/migrations/0009_eventpagechoosermodel.py b/wagtail/tests/migrations/0009_eventpagechoosermodel.py deleted file mode 100644 index 6b6e505d7..000000000 --- a/wagtail/tests/migrations/0009_eventpagechoosermodel.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tests', '0008_registerdecorator'), - ] - - operations = [ - migrations.CreateModel( - name='EventPageChooserModel', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('page', models.ForeignKey(help_text='more help text', to='tests.EventPage')), - ], - options={ - }, - bases=(models.Model,), - ), - ] diff --git a/wagtail/tests/migrations/0010_customimagewithadminformfields_customimagewithoutadminformfields.py b/wagtail/tests/migrations/0010_customimagewithadminformfields_customimagewithoutadminformfields.py deleted file mode 100644 index 1b7f7c9b9..000000000 --- a/wagtail/tests/migrations/0010_customimagewithadminformfields_customimagewithoutadminformfields.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import wagtail.wagtailadmin.taggable -from django.conf import settings -import taggit.managers -import wagtail.wagtailimages.models - - -class Migration(migrations.Migration): - - dependencies = [ - ('taggit', '0001_initial'), - ('tests', '0009_eventpagechoosermodel'), - ] - - operations = [ - migrations.CreateModel( - name='CustomImageWithAdminFormFields', - fields=[ - ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), - ('title', models.CharField(verbose_name='Title', max_length=255)), - ('file', models.ImageField(verbose_name='File', height_field='height', upload_to=wagtail.wagtailimages.models.get_upload_to, width_field='width')), - ('width', models.IntegerField(editable=False)), - ('height', models.IntegerField(editable=False)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('focal_point_x', models.PositiveIntegerField(blank=True, null=True)), - ('focal_point_y', models.PositiveIntegerField(blank=True, null=True)), - ('focal_point_width', models.PositiveIntegerField(blank=True, null=True)), - ('focal_point_height', models.PositiveIntegerField(blank=True, null=True)), - ('caption', models.CharField(max_length=255)), - ('not_editable_field', models.CharField(max_length=255)), - ('tags', taggit.managers.TaggableManager(verbose_name='Tags', help_text=None, through='taggit.TaggedItem', blank=True, to='taggit.Tag')), - ('uploaded_by_user', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, blank=True, editable=False)), - ], - options={ - 'abstract': False, - }, - bases=(models.Model, wagtail.wagtailadmin.taggable.TagSearchable), - ), - migrations.CreateModel( - name='CustomImageWithoutAdminFormFields', - fields=[ - ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), - ('title', models.CharField(verbose_name='Title', max_length=255)), - ('file', models.ImageField(verbose_name='File', height_field='height', upload_to=wagtail.wagtailimages.models.get_upload_to, width_field='width')), - ('width', models.IntegerField(editable=False)), - ('height', models.IntegerField(editable=False)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('focal_point_x', models.PositiveIntegerField(blank=True, null=True)), - ('focal_point_y', models.PositiveIntegerField(blank=True, null=True)), - ('focal_point_width', models.PositiveIntegerField(blank=True, null=True)), - ('focal_point_height', models.PositiveIntegerField(blank=True, null=True)), - ('caption', models.CharField(max_length=255)), - ('not_editable_field', models.CharField(max_length=255)), - ('tags', taggit.managers.TaggableManager(verbose_name='Tags', help_text=None, through='taggit.TaggedItem', blank=True, to='taggit.Tag')), - ('uploaded_by_user', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, blank=True, editable=False)), - ], - options={ - 'abstract': False, - }, - bases=(models.Model, wagtail.wagtailadmin.taggable.TagSearchable), - ), - ] diff --git a/wagtail/tests/routablepage/__init__.py b/wagtail/tests/routablepage/__init__.py new file mode 100644 index 000000000..87fe2ba4a --- /dev/null +++ b/wagtail/tests/routablepage/__init__.py @@ -0,0 +1 @@ +default_app_config = 'wagtail.tests.routablepage.apps.WagtailRoutablePageTestsAppConfig' diff --git a/wagtail/tests/routablepage/apps.py b/wagtail/tests/routablepage/apps.py new file mode 100644 index 000000000..8a9f69be8 --- /dev/null +++ b/wagtail/tests/routablepage/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class WagtailRoutablePageTestsAppConfig(AppConfig): + name = 'wagtail.tests.routablepage' + label = 'routablepagetests' + verbose_name = "Wagtail routable page tests" diff --git a/wagtail/tests/routablepage/migrations/0001_initial.py b/wagtail/tests/routablepage/migrations/0001_initial.py new file mode 100644 index 000000000..6d5bdfa87 --- /dev/null +++ b/wagtail/tests/routablepage/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import wagtail.contrib.wagtailroutablepage.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0013_update_golive_expire_help_text'), + ] + + operations = [ + migrations.CreateModel( + name='RoutablePageTest', + fields=[ + ('page_ptr', models.OneToOneField(to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True, parent_link=True)), + ], + options={ + 'abstract': False, + }, + bases=(wagtail.contrib.wagtailroutablepage.models.RoutablePageMixin, 'wagtailcore.page'), + ), + ] diff --git a/wagtail/tests/routablepage/migrations/__init__.py b/wagtail/tests/routablepage/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/tests/routablepage/models.py b/wagtail/tests/routablepage/models.py new file mode 100644 index 000000000..0a3c3692c --- /dev/null +++ b/wagtail/tests/routablepage/models.py @@ -0,0 +1,28 @@ +from django.db import models +from django.http import HttpResponse +from django.conf.urls import url + +from wagtail.contrib.wagtailroutablepage.models import RoutablePage + + +def routable_page_external_view(request, arg): + return HttpResponse("EXTERNAL VIEW: " + arg) + +class RoutablePageTest(RoutablePage): + @property + def subpage_urls(self): + return ( + url(r'^$', self.main, name='main'), + url(r'^archive/year/(\d+)/$', self.archive_by_year, name='archive_by_year'), + url(r'^archive/author/(?P.+)/$', self.archive_by_author, name='archive_by_author'), + url(r'^external/(.+)/$', routable_page_external_view, name='external_view') + ) + + def archive_by_year(self, request, year): + return HttpResponse("ARCHIVE BY YEAR: " + str(year)) + + def archive_by_author(self, request, author_slug): + return HttpResponse("ARCHIVE BY AUTHOR: " + author_slug) + + def main(self, request): + return HttpResponse("MAIN VIEW") diff --git a/wagtail/tests/search/__init__.py b/wagtail/tests/search/__init__.py new file mode 100644 index 000000000..f28f75cff --- /dev/null +++ b/wagtail/tests/search/__init__.py @@ -0,0 +1 @@ +default_app_config = 'wagtail.tests.search.apps.WagtailSearchTestsAppConfig' diff --git a/wagtail/tests/search/apps.py b/wagtail/tests/search/apps.py new file mode 100644 index 000000000..a8e8c5ee3 --- /dev/null +++ b/wagtail/tests/search/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class WagtailSearchTestsAppConfig(AppConfig): + name = 'wagtail.tests.search' + label = 'searchtests' + verbose_name = "Wagtail search tests" diff --git a/wagtail/tests/search/migrations/0001_initial.py b/wagtail/tests/search/migrations/0001_initial.py new file mode 100644 index 000000000..6359e5355 --- /dev/null +++ b/wagtail/tests/search/migrations/0001_initial.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import wagtail.wagtailsearch.index + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='SearchTest', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)), + ('title', models.CharField(max_length=255)), + ('content', models.TextField()), + ('live', models.BooleanField(default=False)), + ('published_date', models.DateField(null=True)), + ], + options={ + }, + bases=(models.Model, wagtail.wagtailsearch.index.Indexed), + ), + migrations.CreateModel( + name='SearchTestChild', + fields=[ + ('searchtest_ptr', models.OneToOneField(primary_key=True, serialize=False, parent_link=True, to='searchtests.SearchTest', auto_created=True)), + ('subtitle', models.CharField(null=True, max_length=255, blank=True)), + ('extra_content', models.TextField()), + ], + options={ + }, + bases=('searchtests.searchtest',), + ), + ] diff --git a/wagtail/tests/search/migrations/__init__.py b/wagtail/tests/search/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/tests/search/models.py b/wagtail/tests/search/models.py new file mode 100644 index 000000000..d5f646576 --- /dev/null +++ b/wagtail/tests/search/models.py @@ -0,0 +1,54 @@ +from django.db import models + +from wagtail.wagtailsearch import index + + +class SearchTest(models.Model, index.Indexed): + title = models.CharField(max_length=255) + content = models.TextField() + live = models.BooleanField(default=False) + published_date = models.DateField(null=True) + + search_fields = [ + index.SearchField('title', partial_match=True), + index.SearchField('content'), + index.SearchField('callable_indexed_field'), + index.FilterField('title'), + index.FilterField('live'), + index.FilterField('published_date'), + ] + + def callable_indexed_field(self): + return "Callable" + + @classmethod + def get_indexed_objects(cls): + indexed_objects = super(SearchTest, cls).get_indexed_objects() + + # Exclude SearchTests that have a SearchTestChild to stop update_index creating duplicates + if cls is SearchTest: + indexed_objects = indexed_objects.exclude( + id__in=SearchTestChild.objects.all().values_list('searchtest_ptr_id', flat=True) + ) + + # Exclude SearchTests that have the title "Don't index me!" + indexed_objects = indexed_objects.exclude(title="Don't index me!") + + return indexed_objects + + def get_indexed_instance(self): + # Check if there is a SearchTestChild that descends from this + child = SearchTestChild.objects.filter(searchtest_ptr_id=self.id).first() + + # Return the child if there is one, otherwise return self + return child or self + + +class SearchTestChild(SearchTest): + subtitle = models.CharField(max_length=255, null=True, blank=True) + extra_content = models.TextField() + + search_fields = SearchTest.search_fields + [ + index.SearchField('subtitle', partial_match=True), + index.SearchField('extra_content'), + ] diff --git a/wagtail/tests/settings.py b/wagtail/tests/settings.py index 3366af818..92b058af0 100644 --- a/wagtail/tests/settings.py +++ b/wagtail/tests/settings.py @@ -76,7 +76,12 @@ INSTALLED_APPS = ( 'wagtail.contrib.wagtailsitemaps', 'wagtail.contrib.wagtailroutablepage', 'wagtail.contrib.wagtailfrontendcache', - 'wagtail.tests', + 'wagtail.tests.testapp', + 'wagtail.tests.demosite', + 'wagtail.tests.customuser', + 'wagtail.tests.snippets', + 'wagtail.tests.routablepage', + 'wagtail.tests.search', # Install wagtailredirects with its appconfig # Theres nothing special about wagtailredirects, we just need to have one @@ -110,7 +115,7 @@ WAGTAILSEARCH_BACKENDS = { } } -AUTH_USER_MODEL = 'tests.CustomUser' +AUTH_USER_MODEL = 'customuser.CustomUser' try: # Only add Elasticsearch backend if the elasticsearch-py library is installed diff --git a/wagtail/tests/snippets/__init__.py b/wagtail/tests/snippets/__init__.py new file mode 100644 index 000000000..96bbbaa54 --- /dev/null +++ b/wagtail/tests/snippets/__init__.py @@ -0,0 +1 @@ +default_app_config = 'wagtail.tests.snippets.apps.WagtailSnippetsTestsAppConfig' diff --git a/wagtail/tests/snippets/apps.py b/wagtail/tests/snippets/apps.py new file mode 100644 index 000000000..26004ec2b --- /dev/null +++ b/wagtail/tests/snippets/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class WagtailSnippetsTestsAppConfig(AppConfig): + name = 'wagtail.tests.snippets' + label = 'snippetstests' + verbose_name = "Wagtail snippets tests" diff --git a/wagtail/tests/snippets/migrations/0001_initial.py b/wagtail/tests/snippets/migrations/0001_initial.py new file mode 100644 index 000000000..b348bd2f5 --- /dev/null +++ b/wagtail/tests/snippets/migrations/0001_initial.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='AlphaSnippet', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('text', models.CharField(max_length=255)), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='RegisterDecorator', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='RegisterFunction', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ZuluSnippet', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('text', models.CharField(max_length=255)), + ], + options={ + }, + bases=(models.Model,), + ), + ] diff --git a/wagtail/tests/snippets/migrations/__init__.py b/wagtail/tests/snippets/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/tests/snippets/models.py b/wagtail/tests/snippets/models.py new file mode 100644 index 000000000..c3b9512a0 --- /dev/null +++ b/wagtail/tests/snippets/models.py @@ -0,0 +1,39 @@ +from django.db import models +from django.utils.encoding import python_2_unicode_compatible + +from wagtail.wagtailsnippets.models import register_snippet + + +# AlphaSnippet and ZuluSnippet are for testing ordering of +# snippets when registering. They are named as such to ensure +# thier ordering is clear. They are registered during testing +# to ensure specific [in]correct register ordering + +# AlphaSnippet is registered during TestSnippetOrdering +@python_2_unicode_compatible +class AlphaSnippet(models.Model): + text = models.CharField(max_length=255) + + def __str__(self): + return self.text + + +# ZuluSnippet is registered during TestSnippetOrdering +@python_2_unicode_compatible +class ZuluSnippet(models.Model): + text = models.CharField(max_length=255) + + def __str__(self): + return self.text + + +# Register model as snippet using register_snippet as both a function and a decorator + +class RegisterFunction(models.Model): + pass +register_snippet(RegisterFunction) + +@register_snippet +class RegisterDecorator(models.Model): + pass + diff --git a/wagtail/tests/templates/tests/event_index.html b/wagtail/tests/templates/tests/event_index.html deleted file mode 100644 index dc07020b2..000000000 --- a/wagtail/tests/templates/tests/event_index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - {{ self.title }} - - -

{{ self.title }}

- {% include "tests/includes/event_listing.html" %} - - diff --git a/wagtail/tests/templates/tests/event_page.html b/wagtail/tests/templates/tests/event_page.html deleted file mode 100644 index a3ccaa718..000000000 --- a/wagtail/tests/templates/tests/event_page.html +++ /dev/null @@ -1,17 +0,0 @@ -{% load wagtailcore_tags wagtailimages_tags %} - - - - - Event: {{ self.title }} - - -

{{ self.title }}

-

Event

- {% if self.feed_image %} - {% image self.feed_image width-200 class="feed-image" %} - {% endif %} - {{ self.body|richtext }} -

Back to events index

- - diff --git a/wagtail/tests/templates/tests/event_page_password_required.html b/wagtail/tests/templates/tests/event_page_password_required.html deleted file mode 100644 index e58d740ad..000000000 --- a/wagtail/tests/templates/tests/event_page_password_required.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - {{ self.title }} - - -

{{ self.title }}

-

This event is invitation only. Please enter your password to see the details.

-
- {% csrf_token %} - {{ form.as_p }} - -
- - diff --git a/wagtail/tests/templates/tests/form_page.html b/wagtail/tests/templates/tests/form_page.html deleted file mode 100644 index d908c60a2..000000000 --- a/wagtail/tests/templates/tests/form_page.html +++ /dev/null @@ -1,16 +0,0 @@ -{% load wagtailcore_tags %} - - - - - {{ self.title }} - - -

{{ self.title }}

-
- {% csrf_token %} - {{ form.as_p }} - -
- - diff --git a/wagtail/tests/templates/tests/form_page_landing.html b/wagtail/tests/templates/tests/form_page_landing.html deleted file mode 100644 index d9a6369d4..000000000 --- a/wagtail/tests/templates/tests/form_page_landing.html +++ /dev/null @@ -1,12 +0,0 @@ -{% load wagtailcore_tags %} - - - - - {{ self.title }} - - -

{{ self.title }}

-

Thank you for your feedback.

- - diff --git a/wagtail/tests/templates/tests/simple_page.html b/wagtail/tests/templates/tests/simple_page.html deleted file mode 100644 index 1f3593514..000000000 --- a/wagtail/tests/templates/tests/simple_page.html +++ /dev/null @@ -1,12 +0,0 @@ -{% load wagtailcore_tags %} - - - - - {{ self.title }} - - -

{{ self.title }}

-

Simple page

- - diff --git a/wagtail/tests/testapp/__init__.py b/wagtail/tests/testapp/__init__.py new file mode 100644 index 000000000..accd0aff7 --- /dev/null +++ b/wagtail/tests/testapp/__init__.py @@ -0,0 +1 @@ +default_app_config = 'wagtail.tests.testapp.apps.WagtailTestsAppConfig' diff --git a/wagtail/tests/testapp/apps.py b/wagtail/tests/testapp/apps.py new file mode 100644 index 000000000..8dedd7f25 --- /dev/null +++ b/wagtail/tests/testapp/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class WagtailTestsAppConfig(AppConfig): + name = 'wagtail.tests.testapp' + label = 'tests' + verbose_name = "Wagtail tests" diff --git a/wagtail/tests/fixtures/test.json b/wagtail/tests/testapp/fixtures/test.json similarity index 98% rename from wagtail/tests/fixtures/test.json rename to wagtail/tests/testapp/fixtures/test.json index de8b1e27e..665006443 100644 --- a/wagtail/tests/fixtures/test.json +++ b/wagtail/tests/testapp/fixtures/test.json @@ -461,7 +461,7 @@ }, { "pk": 1, - "model": "tests.customuser", + "model": "customuser.customuser", "fields": { "username": "superuser", "first_name": "", @@ -478,7 +478,7 @@ }, { "pk": 2, - "model": "tests.customuser", + "model": "customuser.customuser", "fields": { "username": "eventeditor", "first_name": "", @@ -496,7 +496,7 @@ }, { "pk": 3, - "model": "tests.customuser", + "model": "customuser.customuser", "fields": { "username": "eventmoderator", "first_name": "", @@ -514,7 +514,7 @@ }, { "pk": 4, - "model": "tests.customuser", + "model": "customuser.customuser", "fields": { "username": "inactiveuser", "first_name": "", @@ -532,7 +532,7 @@ }, { "pk": 5, - "model": "tests.customuser", + "model": "customuser.customuser", "fields": { "username": "siteeditor", "first_name": "", @@ -550,7 +550,7 @@ }, { "pk": 6, - "model": "tests.customuser", + "model": "customuser.customuser", "fields": { "username": "admin_only_user", "first_name": "", diff --git a/wagtail/tests/migrations/0002_auto_20140827_0908.py b/wagtail/tests/testapp/migrations/0001_initial.py similarity index 54% rename from wagtail/tests/migrations/0002_auto_20140827_0908.py rename to wagtail/tests/testapp/migrations/0001_initial.py index 66c85ffb0..0c3af9b25 100644 --- a/wagtail/tests/migrations/0002_auto_20140827_0908.py +++ b/wagtail/tests/testapp/migrations/0001_initial.py @@ -3,29 +3,31 @@ from __future__ import unicode_literals from django.db import models, migrations import django.db.models.deletion -import modelcluster.fields -import wagtail.contrib.wagtailroutablepage.models -import wagtail.wagtailcore.fields -import wagtail.wagtailsearch.index +from django.conf import settings import modelcluster.tags +import wagtail.wagtailimages.models +import wagtail.wagtailadmin.taggable +import modelcluster.fields +import wagtail.wagtailcore.fields +import taggit.managers class Migration(migrations.Migration): dependencies = [ - ('wagtailcore', '0002_initial_data'), + ('wagtailcore', '0013_update_golive_expire_help_text'), ('wagtaildocs', '0002_initial_data'), ('taggit', '0001_initial'), - ('wagtailimages', '0002_initial_data'), - ('tests', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('wagtailimages', '0005_make_filter_spec_unique'), ] operations = [ migrations.CreateModel( name='Advert', fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), - ('url', models.URLField(null=True, blank=True)), + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('url', models.URLField(blank=True, null=True)), ('text', models.CharField(max_length=255)), ], options={ @@ -35,27 +37,18 @@ class Migration(migrations.Migration): migrations.CreateModel( name='AdvertPlacement', fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('colour', models.CharField(max_length=255)), ('advert', models.ForeignKey(to='tests.Advert', related_name='+')), ], options={ }, bases=(models.Model,), ), - migrations.CreateModel( - name='AlphaSnippet', - fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), - ('text', models.CharField(max_length=255)), - ], - options={ - }, - bases=(models.Model,), - ), migrations.CreateModel( name='BusinessChild', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), + ('page_ptr', models.OneToOneField(parent_link=True, primary_key=True, to='wagtailcore.Page', auto_created=True, serialize=False)), ], options={ 'abstract': False, @@ -65,7 +58,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='BusinessIndex', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), + ('page_ptr', models.OneToOneField(parent_link=True, primary_key=True, to='wagtailcore.Page', auto_created=True, serialize=False)), ], options={ 'abstract': False, @@ -75,17 +68,63 @@ class Migration(migrations.Migration): migrations.CreateModel( name='BusinessSubIndex', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), + ('page_ptr', models.OneToOneField(parent_link=True, primary_key=True, to='wagtailcore.Page', auto_created=True, serialize=False)), ], options={ 'abstract': False, }, bases=('wagtailcore.page',), ), + migrations.CreateModel( + name='CustomImageWithAdminFormFields', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('title', models.CharField(max_length=255, verbose_name='Title')), + ('file', models.ImageField(width_field='width', height_field='height', upload_to=wagtail.wagtailimages.models.get_upload_to, verbose_name='File')), + ('width', models.IntegerField(editable=False)), + ('height', models.IntegerField(editable=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('focal_point_x', models.PositiveIntegerField(blank=True, null=True)), + ('focal_point_y', models.PositiveIntegerField(blank=True, null=True)), + ('focal_point_width', models.PositiveIntegerField(blank=True, null=True)), + ('focal_point_height', models.PositiveIntegerField(blank=True, null=True)), + ('caption', models.CharField(max_length=255)), + ('not_editable_field', models.CharField(max_length=255)), + ('tags', taggit.managers.TaggableManager(verbose_name='Tags', to='taggit.Tag', blank=True, through='taggit.TaggedItem', help_text=None)), + ('uploaded_by_user', models.ForeignKey(null=True, blank=True, to=settings.AUTH_USER_MODEL, editable=False)), + ], + options={ + 'abstract': False, + }, + bases=(models.Model, wagtail.wagtailadmin.taggable.TagSearchable), + ), + migrations.CreateModel( + name='CustomImageWithoutAdminFormFields', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('title', models.CharField(max_length=255, verbose_name='Title')), + ('file', models.ImageField(width_field='width', height_field='height', upload_to=wagtail.wagtailimages.models.get_upload_to, verbose_name='File')), + ('width', models.IntegerField(editable=False)), + ('height', models.IntegerField(editable=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('focal_point_x', models.PositiveIntegerField(blank=True, null=True)), + ('focal_point_y', models.PositiveIntegerField(blank=True, null=True)), + ('focal_point_width', models.PositiveIntegerField(blank=True, null=True)), + ('focal_point_height', models.PositiveIntegerField(blank=True, null=True)), + ('caption', models.CharField(max_length=255)), + ('not_editable_field', models.CharField(max_length=255)), + ('tags', taggit.managers.TaggableManager(verbose_name='Tags', to='taggit.Tag', blank=True, through='taggit.TaggedItem', help_text=None)), + ('uploaded_by_user', models.ForeignKey(null=True, blank=True, to=settings.AUTH_USER_MODEL, editable=False)), + ], + options={ + 'abstract': False, + }, + bases=(models.Model, wagtail.wagtailadmin.taggable.TagSearchable), + ), migrations.CreateModel( name='EventIndex', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), + ('page_ptr', models.OneToOneField(parent_link=True, primary_key=True, to='wagtailcore.Page', auto_created=True, serialize=False)), ('intro', wagtail.wagtailcore.fields.RichTextField(blank=True)), ], options={ @@ -96,17 +135,17 @@ class Migration(migrations.Migration): migrations.CreateModel( name='EventPage', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), - ('date_from', models.DateField(null=True, verbose_name='Start date')), - ('date_to', models.DateField(null=True, help_text='Not required if event is on a single day', blank=True, verbose_name='End date')), - ('time_from', models.TimeField(null=True, blank=True, verbose_name='Start time')), - ('time_to', models.TimeField(null=True, blank=True, verbose_name='End time')), + ('page_ptr', models.OneToOneField(parent_link=True, primary_key=True, to='wagtailcore.Page', auto_created=True, serialize=False)), + ('date_from', models.DateField(verbose_name='Start date', null=True)), + ('date_to', models.DateField(blank=True, help_text='Not required if event is on a single day', verbose_name='End date', null=True)), + ('time_from', models.TimeField(blank=True, verbose_name='Start time', null=True)), + ('time_to', models.TimeField(blank=True, verbose_name='End time', null=True)), ('audience', models.CharField(choices=[('public', 'Public'), ('private', 'Private')], max_length=255)), ('location', models.CharField(max_length=255)), ('body', wagtail.wagtailcore.fields.RichTextField(blank=True)), ('cost', models.CharField(max_length=255)), ('signup_link', models.URLField(blank=True)), - ('feed_image', models.ForeignKey(related_name='+', blank=True, to='wagtailimages.Image', on_delete=django.db.models.deletion.SET_NULL, null=True)), + ('feed_image', models.ForeignKey(to='wagtailimages.Image', null=True, related_name='+', blank=True, on_delete=django.db.models.deletion.SET_NULL)), ], options={ 'abstract': False, @@ -116,74 +155,84 @@ class Migration(migrations.Migration): migrations.CreateModel( name='EventPageCarouselItem', fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), - ('sort_order', models.IntegerField(null=True, blank=True, editable=False)), + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('sort_order', models.IntegerField(editable=False, null=True, blank=True)), ('link_external', models.URLField(blank=True, verbose_name='External link')), ('embed_url', models.URLField(blank=True, verbose_name='Embed URL')), ('caption', models.CharField(blank=True, max_length=255)), - ('image', models.ForeignKey(related_name='+', blank=True, to='wagtailimages.Image', on_delete=django.db.models.deletion.SET_NULL, null=True)), - ('link_document', models.ForeignKey(related_name='+', blank=True, to='wagtaildocs.Document', null=True)), + ('image', models.ForeignKey(to='wagtailimages.Image', null=True, related_name='+', blank=True, on_delete=django.db.models.deletion.SET_NULL)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', null=True, related_name='+', blank=True)), ], options={ - 'abstract': False, 'ordering': ['sort_order'], + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='EventPageChooserModel', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('page', models.ForeignKey(to='tests.EventPage', help_text='more help text')), + ], + options={ }, bases=(models.Model,), ), migrations.CreateModel( name='EventPageRelatedLink', fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), - ('sort_order', models.IntegerField(null=True, blank=True, editable=False)), + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('sort_order', models.IntegerField(editable=False, null=True, blank=True)), ('link_external', models.URLField(blank=True, verbose_name='External link')), ('title', models.CharField(help_text='Link title', max_length=255)), - ('link_document', models.ForeignKey(related_name='+', blank=True, to='wagtaildocs.Document', null=True)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', null=True, related_name='+', blank=True)), ], options={ - 'abstract': False, 'ordering': ['sort_order'], + 'abstract': False, }, bases=(models.Model,), ), migrations.CreateModel( name='EventPageSpeaker', fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), - ('sort_order', models.IntegerField(null=True, blank=True, editable=False)), + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('sort_order', models.IntegerField(editable=False, null=True, blank=True)), ('link_external', models.URLField(blank=True, verbose_name='External link')), - ('first_name', models.CharField(blank=True, verbose_name='Name', max_length=255)), - ('last_name', models.CharField(blank=True, verbose_name='Surname', max_length=255)), - ('image', models.ForeignKey(related_name='+', blank=True, to='wagtailimages.Image', on_delete=django.db.models.deletion.SET_NULL, null=True)), - ('link_document', models.ForeignKey(related_name='+', blank=True, to='wagtaildocs.Document', null=True)), + ('first_name', models.CharField(blank=True, max_length=255, verbose_name='Name')), + ('last_name', models.CharField(blank=True, max_length=255, verbose_name='Surname')), + ('image', models.ForeignKey(to='wagtailimages.Image', null=True, related_name='+', blank=True, on_delete=django.db.models.deletion.SET_NULL)), + ('link_document', models.ForeignKey(to='wagtaildocs.Document', null=True, related_name='+', blank=True)), ], options={ - 'abstract': False, 'ordering': ['sort_order'], + 'abstract': False, }, bases=(models.Model,), ), migrations.CreateModel( name='FormField', fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), - ('sort_order', models.IntegerField(null=True, blank=True, editable=False)), + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('sort_order', models.IntegerField(editable=False, null=True, blank=True)), ('label', models.CharField(help_text='The label of the form field', max_length=255)), ('field_type', models.CharField(choices=[('singleline', 'Single line text'), ('multiline', 'Multi-line text'), ('email', 'Email'), ('number', 'Number'), ('url', 'URL'), ('checkbox', 'Checkbox'), ('checkboxes', 'Checkboxes'), ('dropdown', 'Drop down'), ('radio', 'Radio buttons'), ('date', 'Date'), ('datetime', 'Date/time')], max_length=16)), ('required', models.BooleanField(default=True)), - ('choices', models.CharField(blank=True, help_text='Comma seperated list of choices. Only applicable in checkboxes, radio and dropdown.', max_length=512)), - ('default_value', models.CharField(blank=True, help_text='Default value. Comma seperated values supported for checkboxes.', max_length=255)), + ('choices', models.CharField(blank=True, help_text='Comma separated list of choices. Only applicable in checkboxes, radio and dropdown.', max_length=512)), + ('default_value', models.CharField(blank=True, help_text='Default value. Comma separated values supported for checkboxes.', max_length=255)), ('help_text', models.CharField(blank=True, max_length=255)), ], options={ - 'abstract': False, 'ordering': ['sort_order'], + 'abstract': False, }, bases=(models.Model,), ), migrations.CreateModel( name='FormPage', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), + ('page_ptr', models.OneToOneField(parent_link=True, primary_key=True, to='wagtailcore.Page', auto_created=True, serialize=False)), ('to_address', models.CharField(blank=True, help_text='Optional - form submissions will be emailed to this address', max_length=255)), ('from_address', models.CharField(blank=True, max_length=255)), ('subject', models.CharField(blank=True, max_length=255)), @@ -193,10 +242,19 @@ class Migration(migrations.Migration): }, bases=('wagtailcore.page',), ), + migrations.CreateModel( + name='PageChooserModel', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ], + options={ + }, + bases=(models.Model,), + ), migrations.CreateModel( name='PageWithOldStyleRouteMethod', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), + ('page_ptr', models.OneToOneField(parent_link=True, primary_key=True, to='wagtailcore.Page', auto_created=True, serialize=False)), ('content', models.TextField()), ], options={ @@ -204,62 +262,10 @@ class Migration(migrations.Migration): }, bases=('wagtailcore.page',), ), - migrations.CreateModel( - name='RoutablePageTest', - fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), - ], - options={ - 'abstract': False, - }, - bases=(wagtail.contrib.wagtailroutablepage.models.RoutablePageMixin, 'wagtailcore.page'), - ), - migrations.CreateModel( - name='SearchTest', - fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), - ('title', models.CharField(max_length=255)), - ('content', models.TextField()), - ('live', models.BooleanField(default=False)), - ('published_date', models.DateField(null=True)), - ], - options={ - }, - bases=(models.Model, wagtail.wagtailsearch.index.Indexed), - ), - migrations.CreateModel( - name='SearchTestChild', - fields=[ - ('searchtest_ptr', models.OneToOneField(parent_link=True, to='tests.SearchTest', serialize=False, auto_created=True, primary_key=True)), - ('subtitle', models.CharField(null=True, blank=True, max_length=255)), - ('extra_content', models.TextField()), - ], - options={ - }, - bases=('tests.searchtest',), - ), - migrations.CreateModel( - name='SearchTestOldConfig', - fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), - ], - options={ - }, - bases=(models.Model, wagtail.wagtailsearch.index.Indexed), - ), - migrations.CreateModel( - name='SearchTestOldConfigList', - fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), - ], - options={ - }, - bases=(models.Model, wagtail.wagtailsearch.index.Indexed), - ), migrations.CreateModel( name='SimplePage', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), + ('page_ptr', models.OneToOneField(parent_link=True, primary_key=True, to='wagtailcore.Page', auto_created=True, serialize=False)), ('content', models.TextField()), ], options={ @@ -267,10 +273,20 @@ class Migration(migrations.Migration): }, bases=('wagtailcore.page',), ), + migrations.CreateModel( + name='SnippetChooserModel', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('advert', models.ForeignKey(to='tests.Advert', help_text='help text')), + ], + options={ + }, + bases=(models.Model,), + ), migrations.CreateModel( name='StandardChild', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), + ('page_ptr', models.OneToOneField(parent_link=True, primary_key=True, to='wagtailcore.Page', auto_created=True, serialize=False)), ], options={ 'abstract': False, @@ -280,7 +296,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='StandardIndex', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), + ('page_ptr', models.OneToOneField(parent_link=True, primary_key=True, to='wagtailcore.Page', auto_created=True, serialize=False)), ], options={ 'abstract': False, @@ -290,7 +306,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='TaggedPage', fields=[ - ('page_ptr', models.OneToOneField(parent_link=True, to='wagtailcore.Page', serialize=False, auto_created=True, primary_key=True)), + ('page_ptr', models.OneToOneField(parent_link=True, primary_key=True, to='wagtailcore.Page', auto_created=True, serialize=False)), ], options={ 'abstract': False, @@ -300,7 +316,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='TaggedPageTag', fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), ('content_object', modelcluster.fields.ParentalKey(to='tests.TaggedPage', related_name='tagged_items')), ('tag', models.ForeignKey(to='taggit.Tag', related_name='tests_taggedpagetag_items')), ], @@ -309,20 +325,16 @@ class Migration(migrations.Migration): }, bases=(models.Model,), ), - migrations.CreateModel( - name='ZuluSnippet', - fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), - ('text', models.CharField(max_length=255)), - ], - options={ - }, - bases=(models.Model,), - ), migrations.AddField( model_name='taggedpage', name='tags', - field=modelcluster.tags.ClusterTaggableManager(through='tests.TaggedPageTag', blank=True, verbose_name='Tags', to='taggit.Tag', help_text='A comma-separated list of tags.'), + field=modelcluster.tags.ClusterTaggableManager(verbose_name='Tags', to='taggit.Tag', blank=True, through='tests.TaggedPageTag', help_text='A comma-separated list of tags.'), + preserve_default=True, + ), + migrations.AddField( + model_name='pagechoosermodel', + name='page', + field=models.ForeignKey(to='wagtailcore.Page', help_text='help text'), preserve_default=True, ), migrations.AddField( @@ -334,7 +346,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='eventpagespeaker', name='link_page', - field=models.ForeignKey(related_name='+', blank=True, to='wagtailcore.Page', null=True), + field=models.ForeignKey(to='wagtailcore.Page', null=True, related_name='+', blank=True), preserve_default=True, ), migrations.AddField( @@ -346,7 +358,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='eventpagerelatedlink', name='link_page', - field=models.ForeignKey(related_name='+', blank=True, to='wagtailcore.Page', null=True), + field=models.ForeignKey(to='wagtailcore.Page', null=True, related_name='+', blank=True), preserve_default=True, ), migrations.AddField( @@ -358,7 +370,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='eventpagecarouselitem', name='link_page', - field=models.ForeignKey(related_name='+', blank=True, to='wagtailcore.Page', null=True), + field=models.ForeignKey(to='wagtailcore.Page', null=True, related_name='+', blank=True), preserve_default=True, ), migrations.AddField( @@ -373,14 +385,4 @@ class Migration(migrations.Migration): field=modelcluster.fields.ParentalKey(to='wagtailcore.Page', related_name='advert_placements'), preserve_default=True, ), - migrations.AlterField( - model_name='customuser', - name='groups', - field=models.ManyToManyField(related_name='user_set', blank=True, verbose_name='groups', to='auth.Group', related_query_name='user', help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.'), - ), - migrations.AlterField( - model_name='customuser', - name='user_permissions', - field=models.ManyToManyField(related_name='user_set', blank=True, verbose_name='user permissions', to='auth.Permission', related_query_name='user', help_text='Specific permissions for this user.'), - ), ] diff --git a/wagtail/tests/testapp/migrations/__init__.py b/wagtail/tests/testapp/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/tests/models.py b/wagtail/tests/testapp/models.py similarity index 68% rename from wagtail/tests/models.py rename to wagtail/tests/testapp/models.py index bb6f5f007..628997b00 100644 --- a/wagtail/tests/models.py +++ b/wagtail/tests/testapp/models.py @@ -2,10 +2,7 @@ from __future__ import unicode_literals from django.db import models from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger -from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager from django.utils.encoding import python_2_unicode_compatible -from django.conf.urls import url -from django.http import HttpResponse from taggit.models import TaggedItemBase @@ -17,11 +14,10 @@ from wagtail.wagtailcore.fields import RichTextField from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, InlinePanel, PageChooserPanel, TabbedInterface, ObjectList from wagtail.wagtailimages.edit_handlers import ImageChooserPanel from wagtail.wagtaildocs.edit_handlers import DocumentChooserPanel -from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField from wagtail.wagtailsnippets.models import register_snippet +from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel from wagtail.wagtailsearch import index -from wagtail.contrib.wagtailroutablepage.models import RoutablePage from wagtail.wagtailimages.models import AbstractImage, Image @@ -39,50 +35,6 @@ COMMON_PANELS = ( ) -class CustomUserManager(BaseUserManager): - def _create_user(self, username, email, password, - is_staff, is_superuser, **extra_fields): - """ - Creates and saves a User with the given username, email and password. - """ - if not username: - raise ValueError('The given username must be set') - email = self.normalize_email(email) - user = self.model(username=username, email=email, - is_staff=is_staff, is_active=True, - is_superuser=is_superuser, **extra_fields) - user.set_password(password) - user.save(using=self._db) - return user - - def create_user(self, username, email=None, password=None, **extra_fields): - return self._create_user(username, email, password, False, False, - **extra_fields) - - def create_superuser(self, username, email, password, **extra_fields): - return self._create_user(username, email, password, True, True, - **extra_fields) - - -class CustomUser(AbstractBaseUser, PermissionsMixin): - username = models.CharField(max_length=100, unique=True) - email = models.EmailField(max_length=255, blank=True) - is_staff = models.BooleanField(default=True) - is_active = models.BooleanField(default=True) - first_name = models.CharField(max_length=50, blank=True) - last_name = models.CharField(max_length=50, blank=True) - - USERNAME_FIELD = 'username' - - objects = CustomUserManager() - - def get_full_name(self): - return self.first_name + ' ' + self.last_name - - def get_short_name(self): - return self.first_name - - # Link fields class LinkFields(models.Model): @@ -365,29 +317,6 @@ class Advert(models.Model): register_snippet(Advert) -# AlphaSnippet and ZuluSnippet are for testing ordering of -# snippets when registering. They are named as such to ensure -# thier ordering is clear. They are registered during testing -# to ensure specific [in]correct register ordering - -# AlphaSnippet is registered during TestSnippetOrdering -@python_2_unicode_compatible -class AlphaSnippet(models.Model): - text = models.CharField(max_length=255) - - def __str__(self): - return self.text - - -# ZuluSnippet is registered during TestSnippetOrdering -@python_2_unicode_compatible -class ZuluSnippet(models.Model): - text = models.CharField(max_length=255) - - def __str__(self): - return self.text - - class StandardIndex(Page): """ Index for the site, not allowed to be placed anywhere """ parent_page_types = [] @@ -432,77 +361,6 @@ class BusinessChild(Page): parent_page_types = ['tests.BusinessIndex', BusinessSubIndex] -class SearchTest(models.Model, index.Indexed): - title = models.CharField(max_length=255) - content = models.TextField() - live = models.BooleanField(default=False) - published_date = models.DateField(null=True) - - search_fields = [ - index.SearchField('title', partial_match=True), - index.SearchField('content'), - index.SearchField('callable_indexed_field'), - index.FilterField('title'), - index.FilterField('live'), - index.FilterField('published_date'), - ] - - def callable_indexed_field(self): - return "Callable" - - @classmethod - def get_indexed_objects(cls): - indexed_objects = super(SearchTest, cls).get_indexed_objects() - - # Exclude SearchTests that have a SearchTestChild to stop update_index creating duplicates - if cls is SearchTest: - indexed_objects = indexed_objects.exclude( - id__in=SearchTestChild.objects.all().values_list('searchtest_ptr_id', flat=True) - ) - - # Exclude SearchTests that have the title "Don't index me!" - indexed_objects = indexed_objects.exclude(title="Don't index me!") - - return indexed_objects - - def get_indexed_instance(self): - # Check if there is a SearchTestChild that descends from this - child = SearchTestChild.objects.filter(searchtest_ptr_id=self.id).first() - - # Return the child if there is one, otherwise return self - return child or self - -class SearchTestChild(SearchTest): - subtitle = models.CharField(max_length=255, null=True, blank=True) - extra_content = models.TextField() - - search_fields = SearchTest.search_fields + [ - index.SearchField('subtitle', partial_match=True), - index.SearchField('extra_content'), - ] - - -def routable_page_external_view(request, arg): - return HttpResponse("EXTERNAL VIEW: " + arg) - -class RoutablePageTest(RoutablePage): - subpage_urls = ( - url(r'^$', 'main', name='main'), - url(r'^archive/year/(\d+)/$', 'archive_by_year', name='archive_by_year'), - url(r'^archive/author/(?P.+)/$', 'archive_by_author', name='archive_by_author'), - url(r'^external/(.+)/$', routable_page_external_view, name='external_view') - ) - - def archive_by_year(self, request, year): - return HttpResponse("ARCHIVE BY YEAR: " + str(year)) - - def archive_by_author(self, request, author_slug): - return HttpResponse("ARCHIVE BY AUTHOR: " + author_slug) - - def main(self, request): - return HttpResponse("MAIN VIEW") - - class TaggedPageTag(TaggedItemBase): content_object = ParentalKey('tests.TaggedPage', related_name='tagged_items') @@ -530,17 +388,6 @@ class SnippetChooserModel(models.Model): ] -# Register model as snippet using register_snippet as both a function and a decorator - -class RegisterFunction(models.Model): - pass -register_snippet(RegisterFunction) - -@register_snippet -class RegisterDecorator(models.Model): - pass - - class CustomImageWithoutAdminFormFields(AbstractImage): caption = models.CharField(max_length=255) not_editable_field = models.CharField(max_length=255) diff --git a/wagtail/tests/testapp/templates/tests/base.html b/wagtail/tests/testapp/templates/tests/base.html new file mode 100644 index 000000000..636b5c588 --- /dev/null +++ b/wagtail/tests/testapp/templates/tests/base.html @@ -0,0 +1,13 @@ +{% load wagtailuserbar %} + + + + + {% block html_title %}{{ self.title }}{% endblock %} + + + {% wagtailuserbar %} +

{{ self.title }}

+ {% block content %}{% endblock %} + + diff --git a/wagtail/tests/testapp/templates/tests/business_child.html b/wagtail/tests/testapp/templates/tests/business_child.html new file mode 100644 index 000000000..33e29f1c2 --- /dev/null +++ b/wagtail/tests/testapp/templates/tests/business_child.html @@ -0,0 +1,5 @@ +{% extends "tests/base.html" %} + +{% block content %} +

Business Child

+{% endblock %} diff --git a/wagtail/tests/testapp/templates/tests/event_index.html b/wagtail/tests/testapp/templates/tests/event_index.html new file mode 100644 index 000000000..345aac9e0 --- /dev/null +++ b/wagtail/tests/testapp/templates/tests/event_index.html @@ -0,0 +1,5 @@ +{% extends "tests/base.html" %} + +{% block content %} + {% include "tests/includes/event_listing.html" %} +{% endblock %} diff --git a/wagtail/tests/testapp/templates/tests/event_page.html b/wagtail/tests/testapp/templates/tests/event_page.html new file mode 100644 index 000000000..f2f486962 --- /dev/null +++ b/wagtail/tests/testapp/templates/tests/event_page.html @@ -0,0 +1,12 @@ +{% extends "tests/base.html" %} +{% load wagtailcore_tags wagtailimages_tags %} + +{% block html_title %}Event: {{ self.title }}{% endblock %} +{% block content %} +

Event

+ {% if self.feed_image %} + {% image self.feed_image width-200 class="feed-image" %} + {% endif %} + {{ self.body|richtext }} +

Back to events index

+{% endblock %} diff --git a/wagtail/tests/testapp/templates/tests/event_page_password_required.html b/wagtail/tests/testapp/templates/tests/event_page_password_required.html new file mode 100644 index 000000000..7311c7940 --- /dev/null +++ b/wagtail/tests/testapp/templates/tests/event_page_password_required.html @@ -0,0 +1,10 @@ +{% extends "tests/base.html" %} + +{% block content %} +

This event is invitation only. Please enter your password to see the details.

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/wagtail/tests/testapp/templates/tests/form_page.html b/wagtail/tests/testapp/templates/tests/form_page.html new file mode 100644 index 000000000..f275ec80f --- /dev/null +++ b/wagtail/tests/testapp/templates/tests/form_page.html @@ -0,0 +1,10 @@ +{% extends "tests/base.html" %} +{% load wagtailcore_tags %} + +{% block content %} +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/wagtail/tests/testapp/templates/tests/form_page_landing.html b/wagtail/tests/testapp/templates/tests/form_page_landing.html new file mode 100644 index 000000000..9615c79d3 --- /dev/null +++ b/wagtail/tests/testapp/templates/tests/form_page_landing.html @@ -0,0 +1,5 @@ +{% extends "tests/base.html" %} + +{% block content %} +

Thank you for your feedback.

+{% endblock %} diff --git a/wagtail/tests/templates/tests/includes/event_listing.html b/wagtail/tests/testapp/templates/tests/includes/event_listing.html similarity index 100% rename from wagtail/tests/templates/tests/includes/event_listing.html rename to wagtail/tests/testapp/templates/tests/includes/event_listing.html diff --git a/wagtail/tests/testapp/templates/tests/simple_page.html b/wagtail/tests/testapp/templates/tests/simple_page.html new file mode 100644 index 000000000..7a173c096 --- /dev/null +++ b/wagtail/tests/testapp/templates/tests/simple_page.html @@ -0,0 +1,5 @@ +{% extends "tests/base.html" %} + +{% block content %} +

Simple page

+{% endblock %} diff --git a/wagtail/tests/wagtail_hooks.py b/wagtail/tests/testapp/wagtail_hooks.py similarity index 100% rename from wagtail/tests/wagtail_hooks.py rename to wagtail/tests/testapp/wagtail_hooks.py diff --git a/wagtail/wagtailadmin/tasks.py b/wagtail/wagtailadmin/tasks.py deleted file mode 100644 index 815971e46..000000000 --- a/wagtail/wagtailadmin/tasks.py +++ /dev/null @@ -1,101 +0,0 @@ -from django.template.loader import render_to_string -from django.core.mail import send_mail -from django.conf import settings -from django.contrib.auth import get_user_model -from django.db.models import Q - -from wagtail.wagtailcore.models import PageRevision, GroupPagePermission -from wagtail.wagtailusers.models import UserProfile - -# The following will check to see if we can import task from celery - -# if not then we definitely haven't installed it -try: - from celery.decorators import task - NO_CELERY = False -except: - NO_CELERY = True - - -# However, we could have installed celery for other projects. So we will also -# check if we have defined the BROKER_URL setting. If not then definitely we -# haven't configured it. -if NO_CELERY or not hasattr(settings, 'BROKER_URL'): - # So if we enter here we will define a different "task" decorator that - # just returns the original function and sets its delay attribute to - # point to the original function: This way, the send_notification - # function will be actually called instead of the the - # send_notification.delay() - def task(f): - f.delay=f - return f - -def users_with_page_permission(page, permission_type, include_superusers=True): - # Get user model - User = get_user_model() - - # Find GroupPagePermission records of the given type that apply to this page or an ancestor - ancestors_and_self = list(page.get_ancestors()) + [page] - perm = GroupPagePermission.objects.filter(permission_type=permission_type, page__in=ancestors_and_self) - q = Q(groups__page_permissions=perm) - - # Include superusers - if include_superusers: - q |= Q(is_superuser=True) - - return User.objects.filter(is_active=True).filter(q).distinct() - - -@task -def send_notification(page_revision_id, notification, excluded_user_id): - # Get revision - revision = PageRevision.objects.get(id=page_revision_id) - - # Get list of recipients - if notification == 'submitted': - # Get list of publishers - recipients = users_with_page_permission(revision.page, 'publish') - elif notification in ['rejected', 'approved']: - # Get submitter - recipients = [revision.user] - else: - return - - # Get list of email addresses - email_addresses = [ - recipient.email for recipient in recipients - if recipient.email and recipient.id != excluded_user_id and getattr(UserProfile.get_for_user(recipient), notification + '_notifications') - ] - - # Return if there are no email addresses - if not email_addresses: - return - - # Get email subject and content - template = 'wagtailadmin/notifications/' + notification + '.html' - rendered_template = render_to_string(template, dict(revision=revision, settings=settings)).split('\n') - email_subject = rendered_template[0] - email_content = '\n'.join(rendered_template[1:]) - - # Get from email - if hasattr(settings, 'WAGTAILADMIN_NOTIFICATION_FROM_EMAIL'): - from_email = settings.WAGTAILADMIN_NOTIFICATION_FROM_EMAIL - elif hasattr(settings, 'DEFAULT_FROM_EMAIL'): - from_email = settings.DEFAULT_FROM_EMAIL - else: - from_email = 'webmaster@localhost' - - # Send email - send_mail(email_subject, email_content, from_email, email_addresses) - - -@task -def send_email_task(email_subject, email_content, email_addresses, from_email=None): - if not from_email: - if hasattr(settings, 'WAGTAILADMIN_NOTIFICATION_FROM_EMAIL'): - from_email = settings.WAGTAILADMIN_NOTIFICATION_FROM_EMAIL - elif hasattr(settings, 'DEFAULT_FROM_EMAIL'): - from_email = settings.DEFAULT_FROM_EMAIL - else: - from_email = 'webmaster@localhost' - - send_mail(email_subject, email_content, from_email, email_addresses) diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/userbar/item_page_add.html b/wagtail/wagtailadmin/templates/wagtailadmin/userbar/item_page_add.html index 45f0002e9..1a4d0a7f4 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/userbar/item_page_add.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/userbar/item_page_add.html @@ -2,5 +2,5 @@ {% load i18n %} {% block item_content %} - {% trans 'Add' %} -{% endblock %} \ No newline at end of file + {% trans 'Add' %} +{% endblock %} diff --git a/wagtail/wagtailadmin/tests/test_edit_handlers.py b/wagtail/wagtailadmin/tests/test_edit_handlers.py index 74128b3c5..8ff597d87 100644 --- a/wagtail/wagtailadmin/tests/test_edit_handlers.py +++ b/wagtail/wagtailadmin/tests/test_edit_handlers.py @@ -20,7 +20,7 @@ from wagtail.wagtailadmin.widgets import AdminPageChooser, AdminDateInput, Admin from wagtail.wagtailimages.edit_handlers import ImageChooserPanel from wagtail.wagtailcore.models import Page, Site from wagtail.wagtailcore.fields import RichTextArea -from wagtail.tests.models import PageChooserModel, EventPageChooserModel, EventPage, EventPageSpeaker, SimplePage +from wagtail.tests.testapp.models import PageChooserModel, EventPageChooserModel, EventPage, EventPageSpeaker, SimplePage from wagtail.tests.utils import WagtailTestUtils from wagtail.utils.deprecation import RemovedInWagtail12Warning diff --git a/wagtail/wagtailadmin/tests/test_page_chooser.py b/wagtail/wagtailadmin/tests/test_page_chooser.py index 834acde8c..3d67cbfaa 100644 --- a/wagtail/wagtailadmin/tests/test_page_chooser.py +++ b/wagtail/wagtailadmin/tests/test_page_chooser.py @@ -2,7 +2,7 @@ from django.test import TestCase from django.core.urlresolvers import reverse from wagtail.wagtailcore.models import Page -from wagtail.tests.models import SimplePage +from wagtail.tests.testapp.models import SimplePage from wagtail.tests.utils import WagtailTestUtils diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 8bd3634a7..314a31aaf 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -11,7 +11,7 @@ from django.core.paginator import Paginator from django.db.models.signals import pre_delete, post_delete from django.utils import timezone -from wagtail.tests.models import ( +from wagtail.tests.testapp.models import ( SimplePage, EventPage, EventPageCarouselItem, StandardIndex, StandardChild, BusinessIndex, BusinessChild, BusinessSubIndex, diff --git a/wagtail/wagtailadmin/tests/test_privacy.py b/wagtail/wagtailadmin/tests/test_privacy.py index e49b81e0a..bda4fbd8c 100644 --- a/wagtail/wagtailadmin/tests/test_privacy.py +++ b/wagtail/wagtailadmin/tests/test_privacy.py @@ -2,7 +2,7 @@ from django.test import TestCase from django.core.urlresolvers import reverse from wagtail.wagtailcore.models import Page, PageViewRestriction -from wagtail.tests.models import SimplePage +from wagtail.tests.testapp.models import SimplePage from wagtail.tests.utils import WagtailTestUtils diff --git a/wagtail/wagtailadmin/tests/test_userbar.py b/wagtail/wagtailadmin/tests/test_userbar.py index c43a1bbc0..376c91113 100644 --- a/wagtail/wagtailadmin/tests/test_userbar.py +++ b/wagtail/wagtailadmin/tests/test_userbar.py @@ -7,6 +7,7 @@ from django.contrib.auth.models import AnonymousUser from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailcore.models import Page +from wagtail.tests.testapp.models import BusinessIndex, BusinessChild class TestUserbarTag(TestCase): @@ -60,6 +61,37 @@ class TestUserbarFrontend(TestCase, WagtailTestUtils): self.assertEqual(response.status_code, 403) +class TestUserbarAddLink(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def setUp(self): + self.login() + self.homepage = Page.objects.get(url_path='/home/') + self.event_index = Page.objects.get(url_path='/home/events/') + + self.business_index = BusinessIndex(title='Business', slug='business', live=True) + self.homepage.add_child(instance=self.business_index) + + self.business_child = BusinessChild(title='Business Child', slug='child', live=True) + self.business_index.add_child(instance=self.business_child) + + def test_page_allowing_subpages(self): + response = self.client.get(reverse('wagtailadmin_userbar_frontend', args=(self.event_index.id, ))) + + # page allows subpages, so the 'add page' button should show + expected_url = reverse('wagtailadmin_pages_add_subpage', args=(self.event_index.id, )) + expected_link = 'Add' % expected_url + self.assertContains(response, expected_link) + + def test_page_disallowing_subpages(self): + response = self.client.get(reverse('wagtailadmin_userbar_frontend', args=(self.business_child.id, ))) + + # page disallows subpages, so the 'add page' button shouldn't show + expected_url = reverse('wagtailadmin_pages_add_subpage', args=(self.business_index.id, )) + expected_link = 'Add' % expected_url + self.assertNotContains(response, expected_link) + + class TestUserbarModeration(TestCase, WagtailTestUtils): def setUp(self): self.login() diff --git a/wagtail/wagtailadmin/tests/tests.py b/wagtail/wagtailadmin/tests/tests.py index 834c20de5..d4af55f36 100644 --- a/wagtail/wagtailadmin/tests/tests.py +++ b/wagtail/wagtailadmin/tests/tests.py @@ -1,10 +1,10 @@ -from django.test import TestCase +from django.test import TestCase, override_settings from django.core.urlresolvers import reverse from django.core import mail from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailcore.models import Page -from wagtail.wagtailadmin.tasks import send_email_task +from wagtail.wagtailadmin.utils import send_mail class TestHome(TestCase, WagtailTestUtils): @@ -56,15 +56,48 @@ class TestEditorHooks(TestCase, WagtailTestUtils): self.assertContains(response, '') -class TestSendEmailTask(TestCase): +class TestSendMail(TestCase): def test_send_email(self): - send_email_task("Test subject", "Test content", ["nobody@email.com"], "test@email.com") + send_mail("Test subject", "Test content", ["nobody@email.com"], "test@email.com") # Check that the email was sent self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, "Test subject") self.assertEqual(mail.outbox[0].body, "Test content") self.assertEqual(mail.outbox[0].to, ["nobody@email.com"]) + self.assertEqual(mail.outbox[0].from_email, "test@email.com") + + @override_settings(WAGTAILADMIN_NOTIFICATION_FROM_EMAIL='anothertest@email.com') + def test_send_fallback_to_wagtailadmin_notification_from_email_setting(self): + send_mail("Test subject", "Test content", ["nobody@email.com"]) + + # Check that the email was sent + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].subject, "Test subject") + self.assertEqual(mail.outbox[0].body, "Test content") + self.assertEqual(mail.outbox[0].to, ["nobody@email.com"]) + self.assertEqual(mail.outbox[0].from_email, "anothertest@email.com") + + @override_settings(DEFAULT_FROM_EMAIL='yetanothertest@email.com') + def test_send_fallback_to_default_from_email_setting(self): + send_mail("Test subject", "Test content", ["nobody@email.com"]) + + # Check that the email was sent + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].subject, "Test subject") + self.assertEqual(mail.outbox[0].body, "Test content") + self.assertEqual(mail.outbox[0].to, ["nobody@email.com"]) + self.assertEqual(mail.outbox[0].from_email, "yetanothertest@email.com") + + def test_send_default_from_email(self): + send_mail("Test subject", "Test content", ["nobody@email.com"]) + + # Check that the email was sent + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].subject, "Test subject") + self.assertEqual(mail.outbox[0].body, "Test content") + self.assertEqual(mail.outbox[0].to, ["nobody@email.com"]) + self.assertEqual(mail.outbox[0].from_email, "webmaster@localhost") class TestExplorerNavView(TestCase, WagtailTestUtils): diff --git a/wagtail/wagtailadmin/userbar.py b/wagtail/wagtailadmin/userbar.py index 195e936b3..8416255ff 100644 --- a/wagtail/wagtailadmin/userbar.py +++ b/wagtail/wagtailadmin/userbar.py @@ -23,8 +23,8 @@ class AddPageItem(BaseItem): if not request.user.has_perm('wagtailadmin.access_admin'): return "" - # Don't render if user doesn't have ability to add siblings - permission_checker = self.page.get_parent().permissions_for_user(request.user) + # Don't render if user doesn't have ability to add children here + permission_checker = self.page.permissions_for_user(request.user) if not permission_checker.can_add_subpage(): return "" diff --git a/wagtail/wagtailadmin/utils.py b/wagtail/wagtailadmin/utils.py index 043b8336d..a5cb98a1a 100644 --- a/wagtail/wagtailadmin/utils.py +++ b/wagtail/wagtailadmin/utils.py @@ -1,6 +1,13 @@ +from django.template.loader import render_to_string +from django.core.mail import send_mail as django_send_mail +from django.conf import settings +from django.contrib.auth import get_user_model +from django.db.models import Q + from modelcluster.fields import ParentalKey -from wagtail.wagtailcore.models import Page +from wagtail.wagtailcore.models import Page, PageRevision, GroupPagePermission +from wagtail.wagtailusers.models import UserProfile def get_object_usage(obj): @@ -34,3 +41,65 @@ def get_object_usage(obj): ) return pages + + +def users_with_page_permission(page, permission_type, include_superusers=True): + # Get user model + User = get_user_model() + + # Find GroupPagePermission records of the given type that apply to this page or an ancestor + ancestors_and_self = list(page.get_ancestors()) + [page] + perm = GroupPagePermission.objects.filter(permission_type=permission_type, page__in=ancestors_and_self) + q = Q(groups__page_permissions=perm) + + # Include superusers + if include_superusers: + q |= Q(is_superuser=True) + + return User.objects.filter(is_active=True).filter(q).distinct() + + +def send_mail(email_subject, email_content, email_addresses, from_email=None): + if not from_email: + if hasattr(settings, 'WAGTAILADMIN_NOTIFICATION_FROM_EMAIL'): + from_email = settings.WAGTAILADMIN_NOTIFICATION_FROM_EMAIL + elif hasattr(settings, 'DEFAULT_FROM_EMAIL'): + from_email = settings.DEFAULT_FROM_EMAIL + else: + from_email = 'webmaster@localhost' + + django_send_mail(email_subject, email_content, from_email, email_addresses) + + +def send_notification(page_revision_id, notification, excluded_user_id): + # Get revision + revision = PageRevision.objects.get(id=page_revision_id) + + # Get list of recipients + if notification == 'submitted': + # Get list of publishers + recipients = users_with_page_permission(revision.page, 'publish') + elif notification in ['rejected', 'approved']: + # Get submitter + recipients = [revision.user] + else: + return + + # Get list of email addresses + email_addresses = [ + recipient.email for recipient in recipients + if recipient.email and recipient.id != excluded_user_id and getattr(UserProfile.get_for_user(recipient), notification + '_notifications') + ] + + # Return if there are no email addresses + if not email_addresses: + return + + # Get email subject and content + template = 'wagtailadmin/notifications/' + notification + '.html' + rendered_template = render_to_string(template, dict(revision=revision, settings=settings)).split('\n') + email_subject = rendered_template[0] + email_content = '\n'.join(rendered_template[1:]) + + # Send email + send_mail(email_subject, email_content, email_addresses) diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index a707d1df6..e1dd99a58 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -15,7 +15,8 @@ from django.db.models import Count from wagtail.wagtailadmin.edit_handlers import TabbedInterface, ObjectList from wagtail.wagtailadmin.forms import SearchForm, CopyForm -from wagtail.wagtailadmin import tasks, signals +from wagtail.wagtailadmin.utils import send_notification +from wagtail.wagtailadmin import signals from wagtail.wagtailcore import hooks from wagtail.wagtailcore.models import Page, PageRevision, get_navigation_menu_items @@ -225,7 +226,7 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_ messages.success(request, _("Page '{0}' published.").format(page.title)) elif is_submitting: messages.success(request, _("Page '{0}' submitted for moderation.").format(page.title)) - tasks.send_notification.delay(page.get_latest_revision().id, 'submitted', request.user.id) + send_notification(page.get_latest_revision().id, 'submitted', request.user.id) else: messages.success(request, _("Page '{0}' created.").format(page.title)) @@ -361,7 +362,7 @@ def edit(request, page_id): messages.button(reverse('wagtailadmin_pages_view_draft', args=(page_id,)), _('View draft')), messages.button(reverse('wagtailadmin_pages_edit', args=(page_id,)), _('Edit')) ]) - tasks.send_notification.delay(page.get_latest_revision().id, 'submitted', request.user.id) + send_notification(page.get_latest_revision().id, 'submitted', request.user.id) else: messages.success(request, _("Page '{0}' updated.").format(page.title)) @@ -782,7 +783,7 @@ def approve_moderation(request, revision_id): if request.method == 'POST': revision.approve_moderation() messages.success(request, _("Page '{0}' published.").format(revision.page.title)) - tasks.send_notification.delay(revision.id, 'approved', request.user.id) + send_notification(revision.id, 'approved', request.user.id) return redirect('wagtailadmin_home') @@ -799,7 +800,7 @@ def reject_moderation(request, revision_id): if request.method == 'POST': revision.reject_moderation() messages.success(request, _("Page '{0}' rejected for publication.").format(revision.page.title)) - tasks.send_notification.delay(revision.id, 'rejected', request.user.id) + send_notification(revision.id, 'rejected', request.user.id) return redirect('wagtailadmin_home') diff --git a/wagtail/wagtailcore/tests/test_management_commands.py b/wagtail/wagtailcore/tests/test_management_commands.py index 91fc727dc..01544f3c6 100644 --- a/wagtail/wagtailcore/tests/test_management_commands.py +++ b/wagtail/wagtailcore/tests/test_management_commands.py @@ -9,7 +9,7 @@ from django.db import models from wagtail.wagtailcore.models import Page, PageRevision from wagtail.wagtailcore.signals import page_published, page_unpublished -from wagtail.tests.models import SimplePage, EventPage +from wagtail.tests.testapp.models import SimplePage, EventPage class TestFixTreeCommand(TestCase): diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py index a5d0a125b..58b8940de 100644 --- a/wagtail/wagtailcore/tests/test_page_model.py +++ b/wagtail/wagtailcore/tests/test_page_model.py @@ -11,7 +11,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.auth import get_user_model from wagtail.wagtailcore.models import Page, Site -from wagtail.tests.models import EventPage, EventIndex, SimplePage, PageWithOldStyleRouteMethod, BusinessIndex, BusinessSubIndex, BusinessChild, StandardIndex +from wagtail.tests.testapp.models import EventPage, EventIndex, SimplePage, PageWithOldStyleRouteMethod, BusinessIndex, BusinessSubIndex, BusinessChild, StandardIndex class TestSiteRouting(TestCase): diff --git a/wagtail/wagtailcore/tests/test_page_permissions.py b/wagtail/wagtailcore/tests/test_page_permissions.py index bd2bd3f31..33dfc6e21 100644 --- a/wagtail/wagtailcore/tests/test_page_permissions.py +++ b/wagtail/wagtailcore/tests/test_page_permissions.py @@ -2,7 +2,7 @@ from django.test import TestCase from django.contrib.auth import get_user_model from wagtail.wagtailcore.models import Page, UserPagePermissionsProxy -from wagtail.tests.models import EventPage +from wagtail.tests.testapp.models import EventPage class TestPagePermission(TestCase): diff --git a/wagtail/wagtailcore/tests/test_page_queryset.py b/wagtail/wagtailcore/tests/test_page_queryset.py index c39358d93..3dce6c436 100644 --- a/wagtail/wagtailcore/tests/test_page_queryset.py +++ b/wagtail/wagtailcore/tests/test_page_queryset.py @@ -1,7 +1,7 @@ from django.test import TestCase from wagtail.wagtailcore.models import Page, PageViewRestriction -from wagtail.tests.models import EventPage +from wagtail.tests.testapp.models import EventPage class TestPageQuerySet(TestCase): diff --git a/wagtail/wagtailcore/tests/test_rich_text.py b/wagtail/wagtailcore/tests/test_rich_text.py index 8ffb917d8..bc12b4fd7 100644 --- a/wagtail/wagtailcore/tests/test_rich_text.py +++ b/wagtail/wagtailcore/tests/test_rich_text.py @@ -12,7 +12,7 @@ from bs4 import BeautifulSoup class TestPageLinkHandler(TestCase): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] def test_get_db_attributes(self): soup = BeautifulSoup( diff --git a/wagtail/wagtailcore/tests/tests.py b/wagtail/wagtailcore/tests/tests.py index fd2e4de93..c71ab8492 100644 --- a/wagtail/wagtailcore/tests/tests.py +++ b/wagtail/wagtailcore/tests/tests.py @@ -7,7 +7,7 @@ from django.utils.safestring import SafeString from wagtail.wagtailcore.models import Page, Site from wagtail.wagtailcore.templatetags.wagtailcore_tags import richtext from wagtail.wagtailcore.utils import resolve_model_string -from wagtail.tests.models import SimplePage +from wagtail.tests.testapp.models import SimplePage class TestPageUrlTags(TestCase): diff --git a/wagtail/wagtaildocs/tests.py b/wagtail/wagtaildocs/tests.py index 863ad8e9c..a7943e492 100644 --- a/wagtail/wagtaildocs/tests.py +++ b/wagtail/wagtaildocs/tests.py @@ -13,7 +13,7 @@ from django.test.utils import override_settings from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailcore.models import Page -from wagtail.tests.models import EventPage, EventPageRelatedLink +from wagtail.tests.testapp.models import EventPage, EventPageRelatedLink from wagtail.wagtaildocs.models import Document from wagtail.wagtaildocs import models @@ -340,7 +340,7 @@ class TestDocumentFilenameProperties(TestCase): class TestUsageCount(TestCase, WagtailTestUtils): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] def setUp(self): self.login() @@ -391,7 +391,7 @@ class TestUsageCount(TestCase, WagtailTestUtils): class TestGetUsage(TestCase, WagtailTestUtils): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] def setUp(self): self.login() @@ -581,7 +581,7 @@ class TestServeView(TestCase): class TestDocumentRichTextLinkHandler(TestCase): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] def test_get_db_attributes(self): soup = BeautifulSoup( diff --git a/wagtail/wagtailembeds/oembed_providers.py b/wagtail/wagtailembeds/oembed_providers.py index bc944f9bd..30df31b85 100644 --- a/wagtail/wagtailembeds/oembed_providers.py +++ b/wagtail/wagtailembeds/oembed_providers.py @@ -76,7 +76,7 @@ OEMBED_ENDPOINTS = { ], "http://api.instagram.com/oembed": [ "^http://instagr\\.am/p/.+$", - "^http://instagram\\.com/p/.+$" + "^http[s]?://instagram\\.com/p/.+$" ], "https://www.slideshare.net/api/oembed/2": [ "^http://www\\.slideshare\\.net/.+$" diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index 755994dbf..ff3278167 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -16,7 +16,7 @@ from django.core.serializers.json import DjangoJSONEncoder from wagtail.wagtailcore.models import Page, Orderable, UserPagePermissionsProxy, get_page_types from wagtail.wagtailadmin.edit_handlers import FieldPanel -from wagtail.wagtailadmin import tasks +from wagtail.wagtailadmin.utils import send_mail from .forms import FormBuilder @@ -194,7 +194,7 @@ class AbstractEmailForm(AbstractForm): if self.to_address: content = '\n'.join([x[1].label + ': ' + form.data.get(x[0]) for x in form.fields.items()]) - tasks.send_email_task.delay(self.subject, content, [self.to_address], self.from_address,) + send_mail(self.subject, content, [self.to_address], self.from_address,) class Meta: diff --git a/wagtail/wagtailforms/tests.py b/wagtail/wagtailforms/tests.py index 25571bcc1..979f2243b 100644 --- a/wagtail/wagtailforms/tests.py +++ b/wagtail/wagtailforms/tests.py @@ -8,7 +8,7 @@ from django.core.urlresolvers import reverse from wagtail.wagtailcore.models import Page from wagtail.wagtailforms.models import FormSubmission from wagtail.wagtailforms.forms import FormBuilder -from wagtail.tests.models import FormPage, FormField +from wagtail.tests.testapp.models import FormPage, FormField class TestFormSubmission(TestCase): diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index c1844f131..a54c581d4 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -261,7 +261,7 @@ def image_delete(sender, instance, **kwargs): def get_image_model(): from django.conf import settings - from django.db.models import get_model + from django.apps import apps try: app_label, model_name = settings.WAGTAILIMAGES_IMAGE_MODEL.split('.') @@ -270,7 +270,7 @@ def get_image_model(): except ValueError: raise ImproperlyConfigured("WAGTAILIMAGES_IMAGE_MODEL must be of the form 'app_label.model_name'") - image_model = get_model(app_label, model_name) + image_model = apps.get_model(app_label, model_name) if image_model is None: raise ImproperlyConfigured("WAGTAILIMAGES_IMAGE_MODEL refers to model '%s' that has not been installed" % settings.WAGTAILIMAGES_IMAGE_MODEL) return image_model diff --git a/wagtail/wagtailimages/tests/test_models.py b/wagtail/wagtailimages/tests/test_models.py index 9abb3b093..5d96a2848 100644 --- a/wagtail/wagtailimages/tests/test_models.py +++ b/wagtail/wagtailimages/tests/test_models.py @@ -13,7 +13,7 @@ from django.db import connection from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailcore.models import Page -from wagtail.tests.models import EventPage, EventPageCarouselItem +from wagtail.tests.testapp.models import EventPage, EventPageCarouselItem from wagtail.wagtailimages.models import Rendition, Filter, SourceImageIOError from wagtail.wagtailimages.rect import Rect @@ -161,7 +161,7 @@ class TestRenditions(TestCase): class TestUsageCount(TestCase): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] def setUp(self): self.image = Image.objects.create( @@ -184,7 +184,7 @@ class TestUsageCount(TestCase): class TestGetUsage(TestCase): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] def setUp(self): self.image = Image.objects.create( diff --git a/wagtail/wagtailimages/tests/test_rich_text.py b/wagtail/wagtailimages/tests/test_rich_text.py index a9f91a468..473a6faab 100644 --- a/wagtail/wagtailimages/tests/test_rich_text.py +++ b/wagtail/wagtailimages/tests/test_rich_text.py @@ -7,8 +7,6 @@ from wagtail.wagtailimages.rich_text import ImageEmbedHandler class TestImageEmbedHandler(TestCase): - fixtures = ['wagtail/tests/fixtures/test.json'] - def test_get_db_attributes(self): soup = BeautifulSoup( 'foo' diff --git a/wagtail/wagtailimages/tests/tests.py b/wagtail/wagtailimages/tests/tests.py index f1f7bbbd9..3520d01d2 100644 --- a/wagtail/wagtailimages/tests/tests.py +++ b/wagtail/wagtailimages/tests/tests.py @@ -13,7 +13,7 @@ from django.db import models from taggit.forms import TagField, TagWidget from wagtail.utils.deprecation import RemovedInWagtail12Warning -from wagtail.tests.models import CustomImageWithAdminFormFields, CustomImageWithoutAdminFormFields +from wagtail.tests.testapp.models import CustomImageWithAdminFormFields, CustomImageWithoutAdminFormFields from wagtail.tests.utils import WagtailTestUtils from wagtail.wagtailimages.utils import generate_signature, verify_signature from wagtail.wagtailimages.rect import Rect diff --git a/wagtail/wagtailsearch/index.py b/wagtail/wagtailsearch/index.py index fa2f317ce..d7326386a 100644 --- a/wagtail/wagtailsearch/index.py +++ b/wagtail/wagtailsearch/index.py @@ -3,6 +3,7 @@ import warnings from six import string_types from django.db import models +from django.apps import apps class Indexed(object): @@ -74,7 +75,7 @@ class Indexed(object): def get_indexed_models(): return [ - model for model in models.get_models() + model for model in apps.get_models() if issubclass(model, Indexed) and not model._meta.abstract ] diff --git a/wagtail/wagtailsearch/tests/test_backends.py b/wagtail/wagtailsearch/tests/test_backends.py index aad3ee8d8..fc181d6f3 100644 --- a/wagtail/wagtailsearch/tests/test_backends.py +++ b/wagtail/wagtailsearch/tests/test_backends.py @@ -8,7 +8,7 @@ from django.conf import settings from django.core import management from wagtail.tests.utils import WagtailTestUtils -from wagtail.tests import models +from wagtail.tests.search import models from wagtail.wagtailsearch.backends import get_search_backend, InvalidSearchBackendError from wagtail.wagtailsearch.backends.db import DBSearch diff --git a/wagtail/wagtailsearch/tests/test_elasticsearch_backend.py b/wagtail/wagtailsearch/tests/test_elasticsearch_backend.py index fadae9a36..8e0be1f3a 100644 --- a/wagtail/wagtailsearch/tests/test_elasticsearch_backend.py +++ b/wagtail/wagtailsearch/tests/test_elasticsearch_backend.py @@ -8,7 +8,7 @@ import json from django.test import TestCase from django.db.models import Q -from wagtail.tests import models +from wagtail.tests.search import models from .test_backends import BackendTests @@ -186,7 +186,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.all(), "Hello") # Check it - expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'tests_searchtest'}}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'searchtests_searchtest'}}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_none_query_string(self): @@ -194,7 +194,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.all(), None) # Check it - expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'tests_searchtest'}}, 'query': {'match_all': {}}}} + expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'searchtests_searchtest'}}, 'query': {'match_all': {}}}} self.assertDictEqual(query.to_es(), expected_result) def test_filter(self): @@ -202,7 +202,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(title="Test"), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'term': {'title_filter': 'Test'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'term': {'title_filter': 'Test'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_and_filter(self): @@ -210,7 +210,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(title="Test", live=True), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'and': [{'term': {'live_filter': True}}, {'term': {'title_filter': 'Test'}}]}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'and': [{'term': {'live_filter': True}}, {'term': {'title_filter': 'Test'}}]}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} # Make sure field filters are sorted (as they can be in any order which may cause false positives) query = query.to_es() @@ -229,7 +229,7 @@ class TestElasticSearchQuery(TestCase): field_filters[:] = sorted(field_filters, key=lambda f: list(f['term'].keys())[0]) # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'or': [{'term': {'live_filter': True}}, {'term': {'title_filter': 'Test'}}]}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'or': [{'term': {'live_filter': True}}, {'term': {'title_filter': 'Test'}}]}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query, expected_result) def test_negated_filter(self): @@ -237,7 +237,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.exclude(live=True), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'not': {'term': {'live_filter': True}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'not': {'term': {'live_filter': True}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_fields(self): @@ -245,7 +245,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.all(), "Hello", fields=['title']) # Check it - expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'tests_searchtest'}}, 'query': {'match': {'title': 'Hello'}}}} + expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'searchtests_searchtest'}}, 'query': {'match': {'title': 'Hello'}}}} self.assertDictEqual(query.to_es(), expected_result) def test_exact_lookup(self): @@ -253,7 +253,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(title__exact="Test"), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'term': {'title_filter': 'Test'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'term': {'title_filter': 'Test'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_none_lookup(self): @@ -261,7 +261,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(title=None), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'missing': {'field': 'title_filter'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'missing': {'field': 'title_filter'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_isnull_true_lookup(self): @@ -269,7 +269,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(title__isnull=True), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'missing': {'field': 'title_filter'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'missing': {'field': 'title_filter'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_isnull_false_lookup(self): @@ -277,7 +277,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(title__isnull=False), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'not': {'missing': {'field': 'title_filter'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'not': {'missing': {'field': 'title_filter'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_startswith_lookup(self): @@ -285,7 +285,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(title__startswith="Test"), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'prefix': {'title_filter': 'Test'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'prefix': {'title_filter': 'Test'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_gt_lookup(self): @@ -295,7 +295,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(published_date__gt=datetime.datetime(2014, 4, 29)), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'range': {'published_date_filter': {'gt': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'range': {'published_date_filter': {'gt': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_lt_lookup(self): @@ -303,7 +303,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(published_date__lt=datetime.datetime(2014, 4, 29)), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'range': {'published_date_filter': {'lt': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'range': {'published_date_filter': {'lt': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_gte_lookup(self): @@ -311,7 +311,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(published_date__gte=datetime.datetime(2014, 4, 29)), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'range': {'published_date_filter': {'gte': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'range': {'published_date_filter': {'gte': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_lte_lookup(self): @@ -319,7 +319,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(published_date__lte=datetime.datetime(2014, 4, 29)), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'range': {'published_date_filter': {'lte': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'range': {'published_date_filter': {'lte': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) def test_range_lookup(self): @@ -330,7 +330,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(published_date__range=(start_date, end_date)), "Hello") # Check it - expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'tests_searchtest'}}, {'range': {'published_date_filter': {'gte': '2014-04-29', 'lte': '2014-08-19'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'range': {'published_date_filter': {'gte': '2014-04-29', 'lte': '2014-08-19'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} self.assertDictEqual(query.to_es(), expected_result) @@ -358,7 +358,7 @@ class TestElasticSearchMapping(TestCase): self.obj.save() def test_get_document_type(self): - self.assertEqual(self.es_mapping.get_document_type(), 'tests_searchtest') + self.assertEqual(self.es_mapping.get_document_type(), 'searchtests_searchtest') def test_get_mapping(self): # Build mapping @@ -366,7 +366,7 @@ class TestElasticSearchMapping(TestCase): # Check expected_result = { - 'tests_searchtest': { + 'searchtests_searchtest': { 'properties': { 'pk': {'index': 'not_analyzed', 'type': 'string', 'store': 'yes', 'include_in_all': False}, 'content_type': {'index': 'not_analyzed', 'type': 'string', 'include_in_all': False}, @@ -384,7 +384,7 @@ class TestElasticSearchMapping(TestCase): self.assertDictEqual(mapping, expected_result) def test_get_document_id(self): - self.assertEqual(self.es_mapping.get_document_id(self.obj), 'tests_searchtest:' + str(self.obj.pk)) + self.assertEqual(self.es_mapping.get_document_id(self.obj), 'searchtests_searchtest:' + str(self.obj.pk)) def test_get_document(self): # Get document @@ -393,7 +393,7 @@ class TestElasticSearchMapping(TestCase): # Check expected_result = { 'pk': str(self.obj.pk), - 'content_type': 'tests_searchtest', + 'content_type': 'searchtests_searchtest', '_partials': ['Hello'], 'live_filter': False, 'published_date_filter': None, @@ -430,7 +430,7 @@ class TestElasticSearchMappingInheritance(TestCase): self.obj.save() def test_get_document_type(self): - self.assertEqual(self.es_mapping.get_document_type(), 'tests_searchtest_tests_searchtestchild') + self.assertEqual(self.es_mapping.get_document_type(), 'searchtests_searchtest_searchtests_searchtestchild') def test_get_mapping(self): # Build mapping @@ -438,7 +438,7 @@ class TestElasticSearchMappingInheritance(TestCase): # Check expected_result = { - 'tests_searchtest_tests_searchtestchild': { + 'searchtests_searchtest_searchtests_searchtestchild': { 'properties': { # New 'extra_content': {'type': 'string', 'include_in_all': True}, @@ -464,7 +464,7 @@ class TestElasticSearchMappingInheritance(TestCase): # This must be tests_searchtest instead of 'tests_searchtest_tests_searchtestchild' # as it uses the contents base content type name. # This prevents the same object being accidentally indexed twice. - self.assertEqual(self.es_mapping.get_document_id(self.obj), 'tests_searchtest:' + str(self.obj.pk)) + self.assertEqual(self.es_mapping.get_document_id(self.obj), 'searchtests_searchtest:' + str(self.obj.pk)) def test_get_document(self): # Build document @@ -481,7 +481,7 @@ class TestElasticSearchMappingInheritance(TestCase): 'subtitle': 'World', # Changed - 'content_type': 'tests_searchtest_tests_searchtestchild', + 'content_type': 'searchtests_searchtest_searchtests_searchtestchild', # Inherited 'pk': str(self.obj.pk), diff --git a/wagtail/wagtailsearch/tests/test_frontend.py b/wagtail/wagtailsearch/tests/test_frontend.py index ba19b4396..e5c754d9b 100644 --- a/wagtail/wagtailsearch/tests/test_frontend.py +++ b/wagtail/wagtailsearch/tests/test_frontend.py @@ -5,7 +5,7 @@ from django.core import paginator from wagtail.wagtailcore.models import Page from wagtail.wagtailsearch.models import Query -from wagtail.tests.models import EventPage +from wagtail.tests.testapp.models import EventPage class TestSearchView(TestCase): diff --git a/wagtail/wagtailsearch/tests/test_indexed_class.py b/wagtail/wagtailsearch/tests/test_indexed_class.py index d4ed79b66..91cc2b51a 100644 --- a/wagtail/wagtailsearch/tests/test_indexed_class.py +++ b/wagtail/wagtailsearch/tests/test_indexed_class.py @@ -3,18 +3,18 @@ import warnings from django.test import TestCase from wagtail.wagtailsearch import index -from wagtail.tests import models +from wagtail.tests.search import models from wagtail.tests.utils import WagtailTestUtils class TestContentTypeNames(TestCase): def test_base_content_type_name(self): name = models.SearchTestChild.indexed_get_toplevel_content_type() - self.assertEqual(name, 'tests_searchtest') + self.assertEqual(name, 'searchtests_searchtest') def test_qualified_content_type_name(self): name = models.SearchTestChild.indexed_get_content_type() - self.assertEqual(name, 'tests_searchtest_tests_searchtestchild') + self.assertEqual(name, 'searchtests_searchtest_searchtests_searchtestchild') class TestSearchFields(TestCase): diff --git a/wagtail/wagtailsearch/tests/test_signal_handlers.py b/wagtail/wagtailsearch/tests/test_signal_handlers.py index ffdd143cf..001b798f9 100644 --- a/wagtail/wagtailsearch/tests/test_signal_handlers.py +++ b/wagtail/wagtailsearch/tests/test_signal_handlers.py @@ -1,7 +1,7 @@ from django.test import TestCase from wagtail.wagtailsearch import signal_handlers -from wagtail.tests import models +from wagtail.tests.search import models class TestGetIndexedInstance(TestCase): diff --git a/wagtail/wagtailsnippets/tests.py b/wagtail/wagtailsnippets/tests.py index 3f41b7e99..996853dad 100644 --- a/wagtail/wagtailsnippets/tests.py +++ b/wagtail/wagtailsnippets/tests.py @@ -2,10 +2,11 @@ from django.http import Http404 from django.test import TestCase from django.core.urlresolvers import reverse from django.db import models +from django.test.utils import override_settings from wagtail.tests.utils import WagtailTestUtils -from django.test.utils import override_settings -from wagtail.tests.models import Advert, AlphaSnippet, ZuluSnippet, SnippetChooserModel, RegisterDecorator, RegisterFunction +from wagtail.tests.testapp.models import Advert, SnippetChooserModel +from wagtail.tests.snippets.models import AlphaSnippet, ZuluSnippet, RegisterDecorator, RegisterFunction from wagtail.wagtailsnippets.models import register_snippet, SNIPPET_MODELS from wagtail.wagtailsnippets.views.snippets import ( @@ -91,7 +92,7 @@ class TestSnippetCreateView(TestCase, WagtailTestUtils): class TestSnippetEditView(TestCase, WagtailTestUtils): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] def setUp(self): self.test_snippet = Advert.objects.get(id=1) @@ -138,7 +139,7 @@ class TestSnippetEditView(TestCase, WagtailTestUtils): class TestSnippetDelete(TestCase, WagtailTestUtils): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] def setUp(self): self.test_snippet = Advert.objects.get(id=1) @@ -160,7 +161,7 @@ class TestSnippetDelete(TestCase, WagtailTestUtils): class TestSnippetChooserPanel(TestCase): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] def setUp(self): model = SnippetChooserModel @@ -213,7 +214,7 @@ class TestSnippetOrdering(TestCase): class TestUsageCount(TestCase): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] @override_settings(WAGTAIL_USAGE_COUNT_ENABLED=True) def test_snippet_usage_count(self): @@ -222,7 +223,7 @@ class TestUsageCount(TestCase): class TestUsedBy(TestCase): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] @override_settings(WAGTAIL_USAGE_COUNT_ENABLED=True) def test_snippet_used_by(self): @@ -231,7 +232,7 @@ class TestUsedBy(TestCase): class TestSnippetChoose(TestCase, WagtailTestUtils): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] def setUp(self): self.login() @@ -255,7 +256,7 @@ class TestSnippetChoose(TestCase, WagtailTestUtils): class TestSnippetChosen(TestCase, WagtailTestUtils): - fixtures = ['wagtail/tests/fixtures/test.json'] + fixtures = ['test.json'] def setUp(self): self.login()