Skip to content
Snippets Groups Projects
Unverified Commit 91af582c authored by Fredrik Jonsson's avatar Fredrik Jonsson Committed by GitHub
Browse files

Merge pull request #1230 from OpenTechFund/feature/972-add-meta-categories-submissions

Allow staff users to see/set meta categories to submissions
parents 737562e8 f68fd235
No related branches found
No related tags found
No related merge requests found
Showing
with 145 additions and 6 deletions
...@@ -90,6 +90,14 @@ class MetaCategory(index.Indexed, MP_Node): ...@@ -90,6 +90,14 @@ class MetaCategory(index.Indexed, MP_Node):
else: else:
super().delete() super().delete()
@classmethod
def get_root_descendants(cls):
# Meta categories queryset without Root node
root_node = cls.get_first_root_node()
if root_node:
return root_node.get_descendants()
return cls.objects.none()
def __str__(self): def __str__(self):
return self.name return self.name
......
from functools import partial
from itertools import groupby
from operator import methodcaller
from django import forms from django import forms
from django.utils.text import mark_safe, slugify from django.utils.text import mark_safe, slugify
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_select2.forms import Select2Widget from django_select2.forms import Select2Widget
from opentech.apply.categories.models import MetaCategory
from opentech.apply.users.models import User from opentech.apply.users.models import User
from .models import AssignedReviewers, ApplicationSubmission, ReviewerRole from .models import AssignedReviewers, ApplicationSubmission, ReviewerRole
from .utils import render_icon from .utils import render_icon
from .widgets import Select2MultiCheckboxesWidget from .widgets import Select2MultiCheckboxesWidget, MetaCategorySelect2Widget
from .workflow import get_action_mapping from .workflow import get_action_mapping
...@@ -321,3 +326,52 @@ class UpdatePartnersForm(forms.ModelForm): ...@@ -321,3 +326,52 @@ class UpdatePartnersForm(forms.ModelForm):
self.submitted_partners self.submitted_partners
) )
return instance return instance
class GroupedModelChoiceIterator(forms.models.ModelChoiceIterator):
def __init__(self, field, groupby):
self.groupby = groupby
super().__init__(field)
def __iter__(self):
if self.field.empty_label is not None:
yield ("", self.field.empty_label)
queryset = self.queryset
# Can't use iterator() when queryset uses prefetch_related()
if not queryset._prefetch_related_lookups:
queryset = queryset.iterator()
for group, objs in groupby(queryset, self.groupby):
yield (group, [self.choice(obj) for obj in objs])
class GroupedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def __init__(self, *args, choices_groupby, **kwargs):
if isinstance(choices_groupby, str):
choices_groupby = methodcaller(choices_groupby)
elif not callable(choices_groupby):
raise TypeError('choices_groupby must either be a str or a callable accepting a single argument')
self.iterator = partial(GroupedModelChoiceIterator, groupby=choices_groupby)
super().__init__(*args, **kwargs)
def label_from_instance(self, obj):
return {'label': super().label_from_instance(obj), 'disabled': not obj.is_leaf()}
class UpdateMetaCategoriesForm(forms.ModelForm):
meta_categories = GroupedModelMultipleChoiceField(
queryset=None, # updated in init method
widget=MetaCategorySelect2Widget(attrs={'data-placeholder': 'Meta categories'}),
label='Meta categories',
choices_groupby='get_parent',
required=False,
help_text='Meta categories are hierarchical in nature.',
)
class Meta:
model = ApplicationSubmission
fields: list = []
def __init__(self, *args, **kwargs):
kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['meta_categories'].queryset = MetaCategory.get_root_descendants().exclude(depth=2)
# Generated by Django 2.0.13 on 2019-05-21 06:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('categories', '0002_metacategory'),
('funds', '0064_group_toggle_end_block'),
]
operations = [
migrations.AddField(
model_name='applicationsubmission',
name='meta_categories',
field=models.ManyToManyField(blank=True, related_name='submissions', to='categories.MetaCategory'),
),
]
...@@ -32,6 +32,7 @@ from wagtail.core.fields import StreamField ...@@ -32,6 +32,7 @@ from wagtail.core.fields import StreamField
from wagtail.contrib.forms.models import AbstractFormSubmission from wagtail.contrib.forms.models import AbstractFormSubmission
from opentech.apply.activity.messaging import messenger, MESSAGES from opentech.apply.activity.messaging import messenger, MESSAGES
from opentech.apply.categories.models import MetaCategory
from opentech.apply.determinations.models import Determination from opentech.apply.determinations.models import Determination
from opentech.apply.review.models import ReviewOpinion from opentech.apply.review.models import ReviewOpinion
from opentech.apply.review.options import MAYBE, AGREE, DISAGREE from opentech.apply.review.options import MAYBE, AGREE, DISAGREE
...@@ -390,6 +391,11 @@ class ApplicationSubmission( ...@@ -390,6 +391,11 @@ class ApplicationSubmission(
limit_choices_to=LIMIT_TO_PARTNERS, limit_choices_to=LIMIT_TO_PARTNERS,
blank=True, blank=True,
) )
meta_categories = models.ManyToManyField(
MetaCategory,
related_name='submissions',
blank=True,
)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True)
search_data = models.TextField() search_data = models.TextField()
...@@ -504,6 +510,7 @@ class ApplicationSubmission( ...@@ -504,6 +510,7 @@ class ApplicationSubmission(
raise ValueError('Incorrect State for transition') raise ValueError('Incorrect State for transition')
submission_in_db = ApplicationSubmission.objects.get(id=self.id) submission_in_db = ApplicationSubmission.objects.get(id=self.id)
prev_meta_categories = submission_in_db.meta_categories.all()
self.id = None self.id = None
self.form_fields = self.get_from_parent('get_defined_fields')(target) self.form_fields = self.get_from_parent('get_defined_fields')(target)
...@@ -511,6 +518,7 @@ class ApplicationSubmission( ...@@ -511,6 +518,7 @@ class ApplicationSubmission(
self.live_revision = None self.live_revision = None
self.draft_revision = None self.draft_revision = None
self.save() self.save()
self.meta_categories.set(prev_meta_categories)
submission_in_db.next = self submission_in_db.next = self
submission_in_db.save() submission_in_db.save()
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
{% include "funds/includes/update_lead_form.html" %} {% include "funds/includes/update_lead_form.html" %}
{% include "funds/includes/update_reviewer_form.html" %} {% include "funds/includes/update_reviewer_form.html" %}
{% include "funds/includes/update_partner_form.html" %} {% include "funds/includes/update_partner_form.html" %}
{% include "funds/includes/update_meta_categories_form.html" %}
{% endblock %} {% endblock %}
{% block reviews %} {% block reviews %}
...@@ -38,6 +39,10 @@ ...@@ -38,6 +39,10 @@
{% include 'funds/includes/screening_status_block.html' %} {% include 'funds/includes/screening_status_block.html' %}
{% endblock %} {% endblock %}
{% block meta_categories %}
{% include 'funds/includes/meta_categories_block.html' %}
{% endblock %}
{% block determination %} {% block determination %}
{% include 'determinations/includes/determination_block.html' with submission=object %} {% include 'determinations/includes/determination_block.html' with submission=object %}
{% endblock %} {% endblock %}
......
...@@ -103,6 +103,8 @@ ...@@ -103,6 +103,8 @@
{% if request.user.is_apply_staff %} {% if request.user.is_apply_staff %}
{% block screening_status %} {% block screening_status %}
{% endblock %} {% endblock %}
{% block meta_categories %}
{% endblock %}
{% endif %} {% endif %}
{% block reviews %} {% block reviews %}
......
...@@ -19,4 +19,6 @@ ...@@ -19,4 +19,6 @@
</div> </div>
<a class="button button--white button--full-width button--bottom-space" href="{% url 'funds:submissions:revisions:list' submission_pk=object.id %}">Revisions</a> <a class="button button--white button--full-width button--bottom-space" href="{% url 'funds:submissions:revisions:list' submission_pk=object.id %}">Revisions</a>
<a data-fancybox data-src="#update-meta-categories" class="button button--white button--full-width button--bottom-space" href="#">Meta Categories</a>
</div> </div>
<div class="sidebar__inner">
<h5>Meta Categories</h5>
<ul>
{% for meta_category in object.meta_categories.all %}
<li>{{ meta_category.name }}</li>
{% empty %}
None. Meta categories can be added to a submission using Meta categories button at top.
{% endfor %}
</ul>
</div>
<div class="sidebar__inner"> <div class="sidebar__inner">
<h5>Screening Status</h5> <h5>Screening Status</h5>
<p> <p>
{{ object.screening_status|default:"Awaiting Screen status" }} {{ object.screening_status|default:"Awaiting Screen status" }}
</p> </p>
</div> </div>
\ No newline at end of file
<div class="modal" id="update-meta-categories">
<h4 class="modal__header-bar">Update Meta Categories</h4>
{% include 'funds/includes/delegated_form_base.html' with form=meta_categories_form value='Update' %}
</div>
...@@ -29,9 +29,10 @@ class TestReviewerFormQueries(TestCase): ...@@ -29,9 +29,10 @@ class TestReviewerFormQueries(TestCase):
# Reviewers # Reviewers
# Partners # Partners
# Meta categories
# Assigned Reviewers # Assigned Reviewers
# Roles # Roles
with self.assertNumQueries(4): with self.assertNumQueries(5):
form = UpdateReviewersForm(user=user, instance=submission) form = UpdateReviewersForm(user=user, instance=submission)
# 3 x Staff - 1 per Role # 3 x Staff - 1 per Role
......
...@@ -45,6 +45,7 @@ from .forms import ( ...@@ -45,6 +45,7 @@ from .forms import (
UpdateReviewersForm, UpdateReviewersForm,
UpdateSubmissionLeadForm, UpdateSubmissionLeadForm,
UpdatePartnersForm, UpdatePartnersForm,
UpdateMetaCategoriesForm,
) )
from .models import ( from .models import (
ApplicationSubmission, ApplicationSubmission,
...@@ -499,6 +500,13 @@ class UpdatePartnersView(DelegatedViewMixin, UpdateView): ...@@ -499,6 +500,13 @@ class UpdatePartnersView(DelegatedViewMixin, UpdateView):
return response return response
@method_decorator(staff_required, name='dispatch')
class UpdateMetaCategoriesView(DelegatedViewMixin, UpdateView):
model = ApplicationSubmission
form_class = UpdateMetaCategoriesForm
context_name = 'meta_categories_form'
class AdminSubmissionDetailView(ReviewContextMixin, ActivityContextMixin, DelegateableView, DetailView): class AdminSubmissionDetailView(ReviewContextMixin, ActivityContextMixin, DelegateableView, DetailView):
template_name_suffix = '_admin_detail' template_name_suffix = '_admin_detail'
model = ApplicationSubmission model = ApplicationSubmission
...@@ -509,6 +517,7 @@ class AdminSubmissionDetailView(ReviewContextMixin, ActivityContextMixin, Delega ...@@ -509,6 +517,7 @@ class AdminSubmissionDetailView(ReviewContextMixin, ActivityContextMixin, Delega
UpdateLeadView, UpdateLeadView,
UpdateReviewersView, UpdateReviewersView,
UpdatePartnersView, UpdatePartnersView,
UpdateMetaCategoriesView,
] ]
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
......
...@@ -20,3 +20,20 @@ class Select2MultiCheckboxesWidget(Select2MultipleWidget): ...@@ -20,3 +20,20 @@ class Select2MultiCheckboxesWidget(Select2MultipleWidget):
attrs = super().build_attrs(*args, **kwargs) attrs = super().build_attrs(*args, **kwargs)
attrs['class'] = attrs['class'].replace('django-select2', 'django-select2-checkboxes') attrs['class'] = attrs['class'].replace('django-select2', 'django-select2-checkboxes')
return attrs return attrs
class MetaCategorySelect2Widget(Select2MultipleWidget):
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
disabled = False
if isinstance(label, dict):
label, disabled = label.get('label'), label.get('disabled')
option_dict = super().create_option(
name, value, label, selected, index, subindex=subindex, attrs=attrs
)
if disabled:
option_dict['attrs']['disabled'] = 'disabled'
return option_dict
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment