Skip to content

Commit

Permalink
Merge pull request #60 from wagtail/tags-and-child-models
Browse files Browse the repository at this point in the history
Tags and child models
  • Loading branch information
jacobtoppm committed Nov 6, 2020
2 parents 9e20909 + 560a305 commit 68a252b
Show file tree
Hide file tree
Showing 11 changed files with 529 additions and 159 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ The following settings are additionally recognised:

By default, objects referenced within imported content will be recursively imported to ensure that those references are still valid on the destination site. However, this is not always desirable - for example, if this happened for the Page model, this would imply that any pages linked from an imported page would get imported as well, along with any pages linked from _those_ pages, and so on, leading to an unpredictable number of extra pages being added anywhere in the page tree as a side-effect of the import. Models listed in `WAGTAILTRANSFER_NO_FOLLOW_MODELS` will thus be skipped in this process, leaving the reference unresolved. The effect this has on the referencing page will vary according to the kind of relation: nullable foreign keys, one-to-many and many-to-many relations will simply omit the missing object; references in rich text and StreamField will become broken links (just as linking a page and then deleting it would); while non-nullable foreign keys will prevent the object from being created at all (meaning that any objects referencing _that_ object will end up with unresolved references, to be handled by the same set of rules).

* `WAGTAILTRANSFER_FOLLOWED_REVERSE_RELATIONS = [('wagtailimages.image', 'tagged_items')]`

Specifies a list of models, their reverse relations to follow, and whether deletions should be synced, when identifying object references that should be imported to the destination site. Defaults to `[('wagtailimages.image', 'tagged_items', True)]`.

By default, Wagtail Transfer will not follow reverse relations (other than importing child models of `ClusterableModel` subclasses) when identifying referenced models. Specifying a `(model, reverse_relationship_name, track_deletions)` in `WAGTAILTRANSFER_FOLLOWED_REVERSE_RELATIONS` means that when
encountering that model and relation, Wagtail Transfer will follow the reverse relationship from the specified model and add the models found to the import if they do not exist on the destination site. This is typically useful in cases such as tags on non-Page models. The `track_deletions` boolean,
if `True`, will delete any models in the reverse relation on the destination site that do not exist in the source site's reverse relation. As a result,
it should only be used for models that behave strictly like child models but do not use `ParentalKey` - for example, tags, where importing an image with deleted tags should delete those tag linking models on the destination site as well.

Note that these settings do not accept models that are defined as subclasses through [multi-table inheritance](https://docs.djangoproject.com/en/stable/topics/db/models/#multi-table-inheritance) - in particular, they cannot be used to define behaviour that only applies to specific subclasses of Page.

* `WAGTAILTRANSFER_CHOOSER_API_PROXY_TIMEOUT = 5`
Expand Down
18 changes: 18 additions & 0 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,30 @@ that object will end up with unresolved references, to be handled by the same se
Note that these settings do not accept models that are defined as subclasses through multi-table inheritance - in
particular, they cannot be used to define behaviour that only applies to specific subclasses of Page.


### `WAGTAILTRANSFER_FOLLOWED_REVERSE_RELATIONS`

```python
WAGTAILTRANSFER_FOLLOWED_REVERSE_RELATIONS = [('wagtailimages.image', 'tagged_items', True)]
```

Specifies a list of models, their reverse relations to follow, and whether deletions should be synced, when identifying object references that should be imported to the destination site. Defaults to `[('wagtailimages.image', 'tagged_items', True)]`.

By default, Wagtail Transfer will not follow reverse relations (other than importing child models of `ClusterableModel` subclasses) when identifying referenced models. Specifying a `(model, reverse_relationship_name, track_deletions)` in `WAGTAILTRANSFER_FOLLOWED_REVERSE_RELATIONS` means that when
encountering that model and relation, Wagtail Transfer will follow the reverse relationship from the specified model and add the models found to the import if they do not exist on the destination site. This is typically useful in cases such as tags on non-Page models. The `track_deletions` boolean,
if `True`, will delete any models in the reverse relation on the destination site that do not exist in the source site's reverse relation. As a result,
it should only be used for models that behave strictly like child models but do not use `ParentalKey` - for example, tags, where importing an image with deleted tags should delete those tag linking models on the destination site as well.


### `WAGTAILTRANSFER_CHOOSER_API_PROXY_TIMEOUT`

```python
WAGTAILTRANSFER_CHOOSER_API_PROXY_TIMEOUT = 5
```

By default, each API call made to browse the page tree on the source server has a timeout limit of 5 seconds. If you find this threshold is too low, you can increase it. This may be of particular use if you are running two local runservers to test or extend Wagtail Transfer.


## Hooks

### `register_field_adapters`
Expand Down
20 changes: 20 additions & 0 deletions tests/migrations/0016_advert_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 3.0.5 on 2020-10-16 11:13

from django.db import migrations
import taggit.managers


class Migration(migrations.Migration):

dependencies = [
('taggit', '0003_taggeditem_add_unique_index'),
('tests', '0015_longadvert'),
]

operations = [
migrations.AddField(
model_name='advert',
name='tags',
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
),
]
2 changes: 2 additions & 0 deletions tests/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.db import models
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from taggit.managers import TaggableManager
from wagtail.core.fields import RichTextField, StreamField
from wagtail.core.models import Orderable, Page
from wagtail.snippets.models import register_snippet
Expand All @@ -13,6 +14,7 @@ class SimplePage(Page):

class Advert(models.Model):
slogan = models.CharField(max_length=255)
tags = TaggableManager()


class LongAdvert(Advert):
Expand Down
2 changes: 2 additions & 0 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@
}
}

WAGTAILTRANSFER_FOLLOWED_REVERSE_RELATIONS = [('wagtailimages.image', 'tagged_items', True), ('tests.advert', 'tagged_items', True)]

WAGTAILTRANSFER_SECRET_KEY = 'i-am-the-local-secret-key'

WAGTAILTRANSFER_UPDATE_RELATED_MODELS = ['wagtailimages.Image', 'tests.advert']
Expand Down
25 changes: 21 additions & 4 deletions tests/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,17 @@ def test_parental_keys(self):
data = json.loads(response.content)

page_data = None
section_data = []
for obj in data['objects']:
if obj['model'] == 'tests.sectionedpage' and obj['pk'] == page.pk:
page_data = obj
break
if obj['model'] == 'tests.sectionedpagesection':
section_data.append(obj)

self.assertEqual(len(page_data['fields']['sections']), 2)
self.assertEqual(page_data['fields']['sections'][0]['model'], 'tests.sectionedpagesection')
self.assertEqual(page_data['fields']['sections'][0]['fields']['title'], "Create the universe")
section_id = page_data['fields']['sections'][0]['pk']
self.assertEqual(section_data[0]['model'], 'tests.sectionedpagesection')
self.assertTrue(section_data[0]['fields']['title'] == "Create the universe")
section_id = page_data['fields']['sections'][0]

# there should also be a uid mapping for the section
matching_uids = [
Expand Down Expand Up @@ -529,6 +531,21 @@ def test_model_with_multi_table_inheritance(self):
self.assertEqual(data['objects'][0]['model'], 'tests.longadvert')
# the child object should be serialized

def test_model_with_tags(self):
# test that a reverse relation such as tagged_items is followed to obtain references to the
# tagged_items, if the model and relationship are specified in WAGTAILTRANSFER_FOLLOWED_REVERSE_RELATIONS
ad = Advert.objects.create(slogan='test')
ad.tags.add('test_tag')

response = self.get({
'tests.advert': [ad.pk]
})
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)

mapped_models = {mapping[0] for mapping in data['mappings']}
self.assertIn('taggit.taggeditem', mapped_models)

def test_image(self):
with open(os.path.join(FIXTURES_DIR, 'wagtail.jpg'), 'rb') as f:
image = Image.objects.create(
Expand Down
Loading

0 comments on commit 68a252b

Please sign in to comment.