Skip to content

Commit

Permalink
adds taxonomy api endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
actlikewill committed Jul 31, 2023
1 parent e116509 commit 6a617b9
Show file tree
Hide file tree
Showing 20 changed files with 505 additions and 221 deletions.
46 changes: 11 additions & 35 deletions indigo/bulk_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def __init__(self, data, errors):
for k, v in data.items():
setattr(self, k, v)

def __getitem__(cls, key):
return cls.__dict__[key]


class LowerChoiceField(forms.ChoiceField):
def to_python(self, value):
Expand Down Expand Up @@ -419,8 +422,7 @@ def update_relationships(self):
self.link_taxonomy(row)

if row.taxonomy_topic:
self.link_taxonomy_topic(row)

self.link_taxonomy(row, model=TaxonomyTopic, attr='taxonomy_topics', attr_single='taxonomy_topic')

if row.amended_by:
self.link_amendment_passive(row)
Expand Down Expand Up @@ -911,56 +913,30 @@ def link_amendment_active(self, row):
task_type='apply-amendment',
amendment=amendment)

def link_taxonomy(self, row):
topics = [x.strip() for x in row.taxonomy.split(';') if x.strip()]
def link_taxonomy(self, row, model=VocabularyTopic, attr='taxonomies', attr_single='taxonomy'):
topics = [x.strip() for x in getattr(row, attr_single).split(';') if x.strip()]
unlinked_topics = []
for t in topics:
topic = VocabularyTopic.get_topic(t)
topic = model.get_topic(t)
if topic:
row.taxonomies.append(topic)
row[attr].append(topic)
if not self.dry_run:
row.work.taxonomies.add(topic)
getattr(row.work, attr).add(topic)
row.work.save_with_revision(self.user)

else:
unlinked_topics.append(t)
if unlinked_topics:
if self.dry_run:
row.notes.append(f'Taxonomy not found: {"; ".join(unlinked_topics)}')

row.notes.append(f'{" ".join(attr_single.split("_")).capitalize()} not found: {"; ".join(unlinked_topics)}')
else:
row.unlinked_topics = "; ".join(unlinked_topics)
try:
existing_task = Task.objects.get(work=row.work, code='link-taxonomy', description__contains=row.unlinked_topics)
except Task.DoesNotExist:
self.create_task(row.work, row, task_type='link-taxonomy')

def link_taxonomy_topic(self, row):
topics = [x.strip() for x in row.taxonomy_topic.split(',') if x.strip()]
unlinked_topics = []

for t in topics:
topic = TaxonomyTopic.objects.filter(slug=t).first()
if topic:
row.taxonomy_topics.append(topic)
if not self.dry_run:
row.work.taxonomy_topics.add(topic)
row.work.save_with_revision(self.user)
else:
unlinked_topics.append(t)
if unlinked_topics:
if self.dry_run:
row.notes.append(f'Taxonomy topic not found: {"; ".join(unlinked_topics)}')
else:
row.unlinked_topics = "; ".join(unlinked_topics)
try:
existing_task = Task.objects.get(work=row.work, code='link-taxonomy-topic', description__contains=row.unlinked_topics)
except Task.DoesNotExist:
self.create_task(row.work, row, task_type='link-taxonomy-topic')





def preview_task(self, row, task_type):
task_preview = task_type.replace('-', ' ')
if task_type == 'import-content':
Expand Down
21 changes: 21 additions & 0 deletions indigo_api/management/commands/migrate_taxonomies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.core.management import BaseCommand
from indigo_api.models import TaxonomyTopic, TaxonomyVocabulary, Work


class Command(BaseCommand):
def handle(self, *args, **kwargs):
for t in TaxonomyVocabulary.objects.all().iterator():
root = TaxonomyTopic.add_root(name=t.title)
for vocabulary in t.topics.all():
if vocabulary.level_1:
child = TaxonomyTopic.add_child(root, name=vocabulary.level_1)
if vocabulary.level_2:
TaxonomyTopic.add_child(child, name=vocabulary.level_2)

for work in Work.objects.all().iterator():
for taxonomy in work.taxonomies.all():
topic = taxonomy.title
level_1 = taxonomy.level_1
level_2 = taxonomy.level_2
topics = TaxonomyTopic.objects.filter(name__in=[topic, level_1, level_2])
work.taxonomy_topics.add(*topics)
66 changes: 66 additions & 0 deletions indigo_api/management/commands/taxonomies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import argparse
import json
import sys

from django.core.management import BaseCommand
from django.db import transaction

from indigo_api.models import TaxonomyTopic


class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"--root", type=str, help="Root of the taxonomy to import or export"
)
parser.add_argument(
"--import", action="store_true", help="Import the taxonomy tree"
)
parser.add_argument(
"--export", action="store_true", help="Export the taxonomy tree"
)
parser.add_argument(
"infile",
nargs="?",
type=argparse.FileType("r"),
default=sys.stdin,
help="File to import from (JSON)",
)
parser.add_argument(
"outfile",
nargs="?",
type=argparse.FileType("w"),
default=sys.stdout,
help="File to export to (JSON)",
)

def handle(self, *args, **kwargs):
if kwargs["import"] and kwargs["export"]:
raise ValueError("Specify only one of --import or --export")

with transaction.atomic():
if kwargs["import"]:
self.do_import(**kwargs)

if kwargs["export"]:
self.do_export(**kwargs)

def do_import(self, **kwargs):
root_node = None
root = kwargs.get("root")
if root:
root_node = TaxonomyTopic.get_root_nodes().filter(name=root).first()
if not root_node:
root_node = TaxonomyTopic.add_root(name=root)
data = json.load(kwargs["infile"])
TaxonomyTopic.load_bulk(data, parent=root_node)

def do_export(self, **kwargs):
root_node = None
root = kwargs.get("root")
if root:
root_node = TaxonomyTopic.get_root_nodes().filter(name=root).first()
if not root_node:
raise ValueError("Root node not found: " + root)
data = TaxonomyTopic.dump_bulk(root_node, keep_ids=False)
json.dump(data, kwargs["outfile"])
6 changes: 3 additions & 3 deletions indigo_api/migrations/0026_taxonomy_topic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 3.2.13 on 2023-07-17 10:52
# Generated by Django 3.2.13 on 2023-07-31 09:20

from django.db import migrations, models

Expand All @@ -18,7 +18,7 @@ class Migration(migrations.Migration):
('depth', models.PositiveIntegerField()),
('numchild', models.PositiveIntegerField(default=0)),
('name', models.CharField(max_length=512)),
('slug', models.SlugField(unique=True)),
('slug', models.SlugField(max_length=512, unique=True)),
],
options={
'verbose_name': 'taxonomy topic',
Expand All @@ -28,6 +28,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='work',
name='taxonomy_topics',
field=models.ManyToManyField(related_name='works', to='indigo_api.TaxonomyTopic'),
field=models.ManyToManyField(blank=True, related_name='works', to='indigo_api.TaxonomyTopic'),
),
]
36 changes: 34 additions & 2 deletions indigo_api/models/works.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from reversion.models import Version
from cobalt import FrbrUri, RepealEvent
from treebeard.mp_tree import MP_Node
from django.urls import reverse
from django.http import QueryDict

from indigo.plugins import plugins

Expand Down Expand Up @@ -94,7 +96,7 @@ def get_topic(self, value):

class TaxonomyTopic(MP_Node):
name = models.CharField(max_length=512, null=False, blank=False)
slug = models.SlugField(null=False, unique=True, blank=False)
slug = models.SlugField(max_length=512, null=False, unique=True, blank=False)
node_order_by = ['name']

class Meta:
Expand All @@ -106,13 +108,43 @@ def __str__(self):

@property
def range_space(self):
# helper for adding space indents in templates
return range(self.depth)

@classmethod
def get_topic(cls, value):
return cls.objects.filter(slug=value).first()

def save(self, *args, **kwargs):
parent = self.get_parent()
self.slug = (f"{parent.slug}-" if parent else "") + slugify(self.name)
super().save(*args, **kwargs)

@classmethod
def get_toc_tree(cls, query_dict):
# capture all preserve all filter parameters and add taxonomy_topic
new_query = query_dict.copy()
new_query.pop('taxonomy_topic', None)

def fix_up(item):
item["title"] = item["data"]["name"]
new_query.update({'taxonomy_topic': item['data']['slug']})
item["href"] = f"?{new_query.urlencode()}"
new_query.pop('taxonomy_topic', None)
for kid in item.get("children", []):
fix_up(kid)

tree = cls.dump_bulk()
for x in tree:
fix_up(x)

tree = [{
"title": "All topics",
"href": f"?{new_query.urlencode()}",
"children": tree
}]
return tree


class WorkMixin(object):
""" Support methods that define behaviour for a work, independent of the database model.
Expand Down Expand Up @@ -419,7 +451,7 @@ class Meta:
# taxonomies
taxonomies = models.ManyToManyField(VocabularyTopic, related_name='works')

taxonomy_topics = models.ManyToManyField(TaxonomyTopic, related_name='works')
taxonomy_topics = models.ManyToManyField(TaxonomyTopic, related_name='works', blank=True)

as_at_date_override = models.DateField(null=True, blank=True, help_text="Date up to which this work was last checked for updates")
consolidation_note_override = models.CharField(max_length=1024, null=True, blank=True, help_text='Consolidation note about this particular work, to override consolidation note for place')
Expand Down
13 changes: 1 addition & 12 deletions indigo_api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,18 +629,7 @@ def get_taxonomies(self, instance):
return taxonomies

def get_taxonomy_topics(self, instance):
taxonomies = []
for t in instance.taxonomy_topics.all():
taxonomies.append({
"name": t.name,
"slug": t.slug
})
for ancestor in t.get_ancestors():
taxonomies.append({
"name": ancestor.name,
"slug": ancestor.slug
})
return taxonomies
return [t.slug for t in instance.taxonomy_topics.all()]


class WorkAmendmentSerializer(serializers.ModelSerializer):
Expand Down
14 changes: 14 additions & 0 deletions indigo_app/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ class TaskFilterForm(forms.Form):
submitted_by = forms.ModelMultipleChoiceField(queryset=User.objects)
type = forms.MultipleChoiceField(choices=Task.CODES)
country = forms.ModelMultipleChoiceField(queryset=Country.objects)
taxonomy_topic = forms.CharField()

def __init__(self, country, *args, **kwargs):
self.country = country
Expand Down Expand Up @@ -338,6 +339,11 @@ def filter_queryset(self, queryset):
queryset = queryset.filter(state__in=['pending_review', 'closed'])\
.filter(submitted_by_user__in=self.cleaned_data['submitted_by'])

if self.cleaned_data.get('taxonomy_topic'):
topic = TaxonomyTopic.objects.filter(slug=self.cleaned_data['taxonomy_topic']).first()
topics = [topic] + [t for t in topic.get_descendants()]
queryset = queryset.filter(work__taxonomy_topics__in=topics)

return queryset

def data_as_url(self):
Expand Down Expand Up @@ -380,6 +386,8 @@ class WorkFilterForm(forms.Form):
advanced_filters = ['assent', 'publication', 'repeal', 'amendment', 'commencement',
'primary_subsidiary', 'taxonomies', 'completeness', 'status']

taxonomy_topic = forms.CharField()

def __init__(self, country, *args, **kwargs):
self.country = country
super(WorkFilterForm, self).__init__(*args, **kwargs)
Expand Down Expand Up @@ -511,6 +519,12 @@ def filter_queryset(self, queryset):
end_date = self.cleaned_data['commencement_date_end']
queryset = queryset.filter(commencements__date__range=[start_date, end_date]).order_by('-commencements__date')

if self.cleaned_data.get('taxonomy_topic'):
topic = TaxonomyTopic.objects.filter(slug=self.cleaned_data['taxonomy_topic']).first()
if topic:
topics = [topic] + [t for t in topic.get_descendants()]
queryset = queryset.filter(taxonomy_topics__in=topics)

return queryset

def filter_document_queryset(self, queryset):
Expand Down
42 changes: 42 additions & 0 deletions indigo_app/js/components/TaxonomyTOC.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<template>
<la-table-of-contents-controller
:items.prop="taxonomy_toc"
collapse-all-btn-classes="btn btn-sm btn-secondary"
expand-all-btn-classes="btn btn-sm btn-secondary"
title-filter-clear-btn-classes="btn btn-sm btn-secondary"
title-filter-input-classes="form-field"
title-filter-placeholder="Filter by topic"
:class="active"
></la-table-of-contents-controller>
</template>

<script>
export default {
name: 'TaxonomyTOC',
data () {
return {
taxonomy_toc: JSON.parse(document.querySelector('#taxonomy_toc').textContent),
};
},
mounted () {
const params = new URLSearchParams(window.location.search);
const toc = document.getElementsByTagName('la-table-of-contents-controller');
toc[0].addEventListener('itemRendered', (e) => {
const tocItem = e.target;
if (!tocItem) return;
const anchor = tocItem.querySelector('.content__action__title');
const href = new URLSearchParams(anchor.getAttribute('href'));
if (params.get('taxonomy_topic') === href.get('taxonomy_topic')) {
anchor.classList.add('active');
}
});
}
};
</script>

<style>
.active {
background-color: #2d7ad4;
color: white;
}
</style>
1 change: 1 addition & 0 deletions indigo_app/js/components/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as DocumentTOCView } from './DocumentTOCView.vue';
export { default as LinterPopup } from './LinterPopup.vue';
export { default as TaxonomyTOC } from './TaxonomyTOC.vue';
Loading

0 comments on commit 6a617b9

Please sign in to comment.