Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Work in progress #1904

Merged
merged 30 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
cbafd53
add work_in_progress, approved_by_user, approved_at, migration
goose-life Dec 6, 2023
1018f27
make approved_at nullable
goose-life Dec 6, 2023
4106169
add 'Change status' to bulk operations
goose-life Dec 6, 2023
69700d1
add approved property
goose-life Dec 6, 2023
6ac0f77
add work in progress badge
goose-life Dec 6, 2023
a44519d
add wip badge in document view, disable publishing document
goose-life Dec 6, 2023
ef860f5
make all new works WIPs
goose-life Dec 6, 2023
7b9277d
add work in progress facet
goose-life Dec 6, 2023
7b81b15
don't use status for work in progress
goose-life Dec 6, 2023
cd05a7b
change permission to bulk_add_work
goose-life Dec 7, 2023
17fd399
don't include user in form, just pass it from the view
goose-life Dec 7, 2023
20aacf2
make wip True by default; backfill existing to False
goose-life Dec 7, 2023
8b84eaa
make new fields blank=True too
goose-life Dec 7, 2023
834ed40
Merge branch 'master' into work-in-progress
goose-life Jan 9, 2024
ec60128
style work form
goose-life Jan 9, 2024
ffdf8cb
tweak consolidation note override, disclaimer
goose-life Jan 9, 2024
2d5b02f
add approve, unapprove buttons, views
goose-life Jan 10, 2024
3f63341
add, send signals; unpublish documents on unapprove
goose-life Jan 10, 2024
367f463
move approve, unapprove onto Work model
goose-life Jan 10, 2024
cb54c68
use approve, unapprove for bulk updates too
goose-life Jan 10, 2024
382b719
handle potential error from the work_approved signal being sent
goose-life Jan 10, 2024
ad8f4bf
approve works on bulk import
goose-life Jan 10, 2024
d633bd4
actions on approve/unapprove; show approval detail
longhotsummer Jan 11, 2024
4b13513
align work-in-progress colour to warning
longhotsummer Jan 11, 2024
005edae
standardise buttons
longhotsummer Jan 11, 2024
331cd22
Update indigo_api/models/works.py
longhotsummer Jan 11, 2024
ab06f96
Update indigo_api/models/works.py
longhotsummer Jan 11, 2024
44c45bb
Merge branch 'master' into work-in-progress
longhotsummer Jan 11, 2024
1396fd7
fixes
longhotsummer Jan 11, 2024
be10a2f
approve work separately from sending work_approved signal
goose-life Jan 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions indigo/bulk_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ def create_or_update(self, row):
def provisionally_save(self, work, old_publication_date=None, new_publication_date=None, old_title=None, new_title=None):
if not self.dry_run:
if not work.pk:
work.work_in_progress = False
work.properties['created_in_bulk'] = True
work.save_with_revision(self.user)
if old_publication_date and new_publication_date:
Expand Down
31 changes: 31 additions & 0 deletions indigo_api/migrations/0029_work_in_progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 3.2.13 on 2023-12-07 11:18

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('indigo_api', '0028_alter_taxonomytopic_slug'),
]

operations = [
migrations.AddField(
model_name='work',
name='approved_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='work',
name='approved_by_user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='work',
name='work_in_progress',
field=models.BooleanField(default=True, help_text='Work in progress, to be approved'),
),
]
24 changes: 24 additions & 0 deletions indigo_api/migrations/0030_backfill_work_in_progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 3.2.13 on 2023-12-07 10:14
from django.db import migrations


def backfill_work_in_progress(apps, schema_editor):
""" Mark existing works as work_in_progress = False; only new ones should be True by default.
"""
Work = apps.get_model('indigo_api', 'Work')
db_alias = schema_editor.connection.alias

for work in Work.objects.using(db_alias).iterator(100):
work.work_in_progress = False
work.save()


class Migration(migrations.Migration):

dependencies = [
('indigo_api', '0029_work_in_progress'),
]

operations = [
migrations.RunPython(backfill_work_in_progress, migrations.RunPython.noop)
]
37 changes: 36 additions & 1 deletion indigo_api/models/works.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from copy import deepcopy
from actstream import action
from datetime import datetime
from django.db.models import JSONField
from django.db import models
from django.db import models, IntegrityError, transaction
from django.db.models import signals, Q
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
Expand All @@ -16,6 +17,7 @@
from treebeard.mp_tree import MP_Node

from indigo.plugins import plugins
from indigo_api.signals import work_approved, work_unapproved
from indigo_api.timeline import TimelineCommencementEvent, describe_single_commencement, get_serialized_timeline


Expand Down Expand Up @@ -499,14 +501,22 @@ class Meta:
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')
disclaimer = models.CharField(max_length=1024, null=True, blank=True, help_text='Disclaimer text about this work')

work_in_progress = models.BooleanField(default=True, help_text="Work in progress, to be approved")

created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
approved_at = models.DateTimeField(null=True, blank=True)

created_by_user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='+')
updated_by_user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='+')
approved_by_user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='+')

objects = WorkManager.from_queryset(WorkQuerySet)()

@property
def approved(self):
return not self.work_in_progress

@property
def locality_code(self):
# Helper to get/set locality using the locality_code, used by the WorkSerializer.
Expand Down Expand Up @@ -638,6 +648,31 @@ def versions(self):
def as_at_date(self):
return self.as_at_date_override or self.place.settings.as_at_date

def approve(self, user, request=None):
self.work_in_progress = False
self.approved_by_user = user
self.approved_at = datetime.now()
self.save_with_revision(user)
action.send(user, verb='approved', action_object=self, place_code=self.place.place_code)
try:
with transaction.atomic():
work_approved.send(sender=self.__class__, work=self, request=request)
except IntegrityError:
pass

def unapprove(self, user):
self.work_in_progress = True
self.approved_by_user = None
self.approved_at = None
self.save_with_revision(user)
action.send(user, verb='unapproved', action_object=self, place_code=self.place.place_code)
work_unapproved.send(sender=self.__class__, work=self)

# unpublish all documents
for document in self.document_set.published():
document.draft = True
document.save_with_revision(user, comment='This document was unpublished because its work was unapproved.')

def __str__(self):
return '%s (%s)' % (self.frbr_uri, self.title)

Expand Down
10 changes: 10 additions & 0 deletions indigo_api/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
"""


work_approved = Signal()
""" A user has approved a work.
"""


work_unapproved = Signal()
""" A user has unapproved a work (marked it as a work in progress).
"""


document_published = Signal(providing_args=["document", "request"])
""" A user has changed a document from draft to published.
"""
Expand Down
29 changes: 27 additions & 2 deletions indigo_app/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ def __init__(self, country, locality, *args, **kwargs):
self.fields[key] = forms.CharField(label=label, required=False)

if self.instance:
# self.fields['commencing_work'].initial = self.instance.commencing_work
self.fields['frbr_doctype'].initial = self.instance.doctype
self.fields['frbr_subtype'].initial = self.instance.subtype
self.fields['frbr_date'].initial = self.instance.date
Expand Down Expand Up @@ -436,6 +435,7 @@ class WorkFilterForm(forms.Form):
repealed_date_end = forms.DateField(input_formats=['%Y-%m-%d'])

stub = forms.MultipleChoiceField(choices=[('stub', 'Stub'), ('not_stub', 'Not a stub'), ])
work_in_progress = forms.MultipleChoiceField(choices=[('work_in_progress', 'Work in progress'), ('approved', 'Approved')])
status = forms.MultipleChoiceField(choices=[('published', 'published'), ('draft', 'draft')])
sortby = forms.ChoiceField(choices=[('-created_at', '-created_at'), ('created_at', 'created_at'), ('-updated_at', '-updated_at'), ('updated_at', 'updated_at'), ('title', 'title'), ('-title', '-title')])
principal = forms.MultipleChoiceField(required=False, choices=[('principal', 'Principal'), ('not_principal', 'Not Principal')])
Expand Down Expand Up @@ -475,6 +475,17 @@ def filter_queryset(self, queryset, exclude=None):
if self.cleaned_data.get('q'):
queryset = queryset.filter(Q(title__icontains=self.cleaned_data['q']) | Q(frbr_uri__icontains=self.cleaned_data['q']))

# filter by work in progress
if exclude != "work_in_progress":
work_in_progress_filter = self.cleaned_data.get('work_in_progress', [])
work_in_progress_qs = Q()
if "work_in_progress" in work_in_progress_filter:
work_in_progress_qs |= Q(work_in_progress=True)
if "approved" in work_in_progress_filter:
work_in_progress_qs |= Q(work_in_progress=False)

queryset = queryset.filter(work_in_progress_qs)

# filter by stub
if exclude != "stub":
stub_filter = self.cleaned_data.get('stub', [])
Expand Down Expand Up @@ -825,11 +836,12 @@ class WorkBulkActionsForm(forms.Form):
del_taxonomy_topics = forms.ModelMultipleChoiceField(
queryset=TaxonomyTopic.objects.all(),
required=False)
change_work_in_progress = forms.ChoiceField(choices=[('', ''), ('approved', 'Approved'), ('work_in_progress', 'Work in progress')], required=False)

def clean_all_work_pks(self):
return self.cleaned_data.get('all_work_pks').split() or []

def save_changes(self):
def save_changes(self, user, request):
if self.cleaned_data.get('add_taxonomy_topics'):
for work in self.cleaned_data['works']:
work.taxonomy_topics.add(*self.cleaned_data['add_taxonomy_topics'])
Expand All @@ -838,6 +850,19 @@ def save_changes(self):
for work in self.cleaned_data['works']:
work.taxonomy_topics.remove(*self.cleaned_data['del_taxonomy_topics'])

if self.cleaned_data.get('change_work_in_progress'):
if self.cleaned_data['change_work_in_progress'] == 'approved':
for work in self.cleaned_data['works']:
# only save if it's changed
if work.work_in_progress:
work.approve(user, request)

else:
for work in self.cleaned_data['works']:
# only save if it's changed
if work.approved:
work.unapprove(user)


class WorkChooserForm(forms.Form):
country = forms.ModelChoiceField(queryset=Country.objects)
Expand Down
4 changes: 4 additions & 0 deletions indigo_app/static/stylesheets/_works.scss
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@
overflow-y: auto;
}
}

.bg-work-in-progress {
background-color: lighten($warning, 45%);
}
37 changes: 18 additions & 19 deletions indigo_app/templates/indigo_api/_work_commencement_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<div class="mb-3 row">
<label class="col-2 col-form-label">{% trans 'Commenced by' %}</label>
<div class="col">
<div class="col-4">
<input type="hidden" id="{{ form.commencing_work.id_for_label }}" name="{{ form.commencing_work.html_name }}" value="{{ form.commencing_work.value|default:'' }}">
{% if commencing_work %}
<div class="mb-2">
Expand All @@ -15,33 +15,32 @@
<div>
{% url 'work_form_commencement' place.place_code as edit_url %}
<button
class="btn btn-outline-primary"
type="button"
hx-get="{% url 'place_work_chooser' place.place_code %}?submit={{ edit_url|urlencode }}&target={{ "#work-form-commencing-work"|urlencode }}&field=work-commencing_work"
hx-target="#work-commencing-work-modal"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#work-commencing-work-modal"
>{% trans 'Choose work' %}</button>
class="btn btn-outline-primary"
type="button"
hx-get="{% url 'place_work_chooser' place.place_code %}?submit={{ edit_url|urlencode }}&target={{ "#work-form-commencing-work"|urlencode }}&field=work-commencing_work"
hx-target="#work-commencing-work-modal"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#work-commencing-work-modal"
>{% trans 'Choose commencing work' %}</button>

{% if commencing_work %}
<button
class="btn btn-outline-danger ms-2"
hx-get="{{ edit_url }}"
hx-target="#work-form-commencing-work"
>Clear</button>
class="btn btn-outline-danger ms-2"
hx-get="{{ edit_url }}"
hx-target="#work-form-commencing-work"
>{% trans 'Clear' %}</button>
{% endif %}
</div>

<div class="form-text text-muted">{% trans 'The work that gives the commencement date of this work.' %}</div>
</div>
<div class="col-6 form-text text-muted">{% trans 'The work that brings this work into force.' %}</div>
</div>

<div
id="work-commencing-work-modal"
class="modal modal-blur fade"
style="display: none"
tabindex="-1"
id="work-commencing-work-modal"
class="modal modal-blur fade"
style="display: none"
tabindex="-1"
>
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content">Loading...</div>
Expand Down
3 changes: 3 additions & 0 deletions indigo_app/templates/indigo_api/_work_info_badges.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@
{% elif not work.commencement_date %}
<span class="badge text-bg-info">{% trans 'commencement date unknown' %}</span>
{% endif %}
{% if work.work_in_progress %}
<span class="badge text-bg-warning">{% trans 'work in progress' %}</span>
{% endif %}
41 changes: 20 additions & 21 deletions indigo_app/templates/indigo_api/_work_parent_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<div class="mb-3 row">
<label class="col-2 col-form-label">{% trans 'Primary work' %}</label>
<div class="col">
<div class="col-4">
<input type="hidden" id="{{ form.parent_work.id_for_label }}" name="{{ form.parent_work.html_name }}" value="{{ form.parent_work.value|default:'' }}">
{% if work.parent_work %}
<div class="mb-2">
Expand All @@ -15,35 +15,34 @@
<div>
{% url 'work_form_parent' place.place_code as edit_url %}
<button
class="btn btn-outline-primary"
type="button"
hx-get="{% url 'place_work_chooser' place.place_code %}?submit={{ edit_url|urlencode }}&target={{ "#work-form-parent"|urlencode }}&field=work-parent_work"
hx-target="#work-parent-modal"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#work-parent-modal"
>{% trans 'Choose parent work' %}</button>
class="btn btn-outline-primary"
type="button"
hx-get="{% url 'place_work_chooser' place.place_code %}?submit={{ edit_url|urlencode }}&target={{ "#work-form-parent"|urlencode }}&field=work-parent_work"
hx-target="#work-parent-modal"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#work-parent-modal"
>{% trans 'Choose primary work' %}</button>

{% if work.parent_work %}
<button
class="btn btn-outline-danger ms-2"
hx-get="{{ edit_url }}"
hx-target="#work-form-parent"
>Clear</button>
class="btn btn-outline-danger ms-2"
hx-get="{{ edit_url }}"
hx-target="#work-form-parent"
>{% trans 'Clear' %}</button>
{% endif %}
</div>
</div>
<div class="col-6 form-text text-muted">
{% trans 'The primary work for regulations is the Act in terms of which they were promulgated.' %}
</div>
</div>

<p class="form-text text-muted">
{% trans 'The primary work for regulations and notices is the primary Act.' %}
</p>

<div
id="work-parent-modal"
class="modal modal-blur fade"
style="display: none"
tabindex="-1"
id="work-parent-modal"
class="modal modal-blur fade"
style="display: none"
tabindex="-1"
>
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content">Loading...</div>
Expand Down
Loading
Loading