diff --git a/opentech/apply/funds/tables.py b/opentech/apply/funds/tables.py index b9d5626c11bfcfcb34d7da0b083ea5d3c48b231d..9028a5e609372b64d9efc095a3f466196fd96868 100644 --- a/opentech/apply/funds/tables.py +++ b/opentech/apply/funds/tables.py @@ -34,14 +34,14 @@ class SubmissionsTable(tables.Table): submit_time = tables.DateColumn(verbose_name="Submitted") phase = tables.Column(verbose_name="Status", order_by=('status',)) stage = tables.Column(verbose_name="Type", order_by=('status',)) - page = tables.Column(verbose_name="Fund") + fund = tables.Column(verbose_name="Fund", accessor='page') comments = tables.Column(accessor='comment_count', verbose_name="Comments") last_update = tables.DateColumn(accessor="last_update", verbose_name="Last updated") class Meta: model = ApplicationSubmission order_by = ('-last_update',) - fields = ('title', 'phase', 'stage', 'page', 'round', 'submit_time', 'last_update') + fields = ('title', 'phase', 'stage', 'fund', 'round', 'submit_time', 'last_update') sequence = fields + ('comments',) template_name = 'funds/tables/table.html' row_attrs = { @@ -69,7 +69,7 @@ class AdminSubmissionsTable(SubmissionsTable): reviews_stats = tables.TemplateColumn(template_name='funds/tables/column_reviews.html', verbose_name=mark_safe("Reviews\n<span>Assgn.\tComp.</span>"), orderable=False) class Meta(SubmissionsTable.Meta): - fields = ('title', 'phase', 'stage', 'page', 'round', 'lead', 'submit_time', 'last_update', 'reviews_stats') # type: ignore + fields = ('title', 'phase', 'stage', 'fund', 'round', 'lead', 'submit_time', 'last_update', 'reviews_stats') # type: ignore sequence = fields + ('comments',) def render_lead(self, value): @@ -129,14 +129,23 @@ class StatusMultipleChoiceFilter(Select2MultipleChoiceFilter): class SubmissionFilter(filters.FilterSet): round = Select2ModelMultipleChoiceFilter(queryset=get_used_rounds, label='Rounds') - funds = Select2ModelMultipleChoiceFilter(name='page', queryset=get_used_funds, label='Funds') + fund = Select2ModelMultipleChoiceFilter(name='page', queryset=get_used_funds, label='Funds') status = StatusMultipleChoiceFilter() lead = Select2ModelMultipleChoiceFilter(queryset=get_round_leads, label='Leads') reviewers = Select2ModelMultipleChoiceFilter(queryset=get_reviewers, label='Reviewers') class Meta: model = ApplicationSubmission - fields = ('funds', 'round', 'status') + fields = ('fund', 'round', 'status') + + def __init__(self, *args, exclude=list(), **kwargs): + super().__init__(*args, **kwargs) + + self.filters = { + field: filter + for field, filter in self.filters.items() + if field not in exclude + } class SubmissionFilterAndSearch(SubmissionFilter): diff --git a/opentech/apply/funds/templates/funds/base_submissions_table.html b/opentech/apply/funds/templates/funds/base_submissions_table.html new file mode 100644 index 0000000000000000000000000000000000000000..43917fa7f2757879bd9163bc8f1a95aabc281c8a --- /dev/null +++ b/opentech/apply/funds/templates/funds/base_submissions_table.html @@ -0,0 +1,41 @@ +{% extends "base-apply.html" %} +{% load static %} +{% load render_table from django_tables2 %} + +{% block extra_css %} +{{ filter.form.media.css }} +{% endblock %} + +{% block content %} + {% block table %} + {% if table.data or active_filters %} + <div class="button button--filters button--contains-icons js-open-filters">Filter By</div> + + <div class="filters js-filter-wrapper"> + <div class="filters__header"> + <div class="js-clear-filters">Clear</div> + <div>Filter by</div> + <div class="js-close-filters">Close</div> + </div> + + <form action="" method="get" class="form form--filters"> + <ul class="form__filters select2 js-filter-list"> + {{ filter.form.as_ul }} + <li> + <button class="button button--primary" type="submit" value="Filter">Filter</button> + </li> + </ul> + </form> + </div> + {% endif %} + + {% render_table table %} + {% endblock %} +{% endblock %} + +{% block extra_js %} + {{ filter.form.media.js }} + <script src="{% static 'js/apply/all-submissions-table.js' %}"></script> + <script src="{% static 'js/apply/submission-filters.js' %}"></script> + <script src="{% static 'js/apply/submission-tooltips.js' %}"></script> +{% endblock %} diff --git a/opentech/apply/funds/templates/funds/submissions.html b/opentech/apply/funds/templates/funds/submissions.html index adf89194bf39019ee4c80427aeab312757190a06..6c9d4af28674db314d01bf61dd7dccbc7e1d3119 100644 --- a/opentech/apply/funds/templates/funds/submissions.html +++ b/opentech/apply/funds/templates/funds/submissions.html @@ -1,12 +1,7 @@ -{% extends "base-apply.html" %} -{% load render_table from django_tables2 %} +{% extends "funds/base_submissions_table.html" %} {% load static %} {% block title %}Submissions{% endblock %} -{% block extra_css %} - {{ filter.form.media.css }} -{% endblock %} - {% block content %} <div class="admin-bar"> <div class="admin-bar__inner wrapper--search"> @@ -21,44 +16,19 @@ </div> <div class="wrapper wrapper--large wrapper--inner-space-medium"> - - {% if table.data or active_filters %} - <div class="button button--filters button--contains-icons js-open-filters">Filter By</div> - - <div class="filters js-filter-wrapper"> - <div class="filters__header"> - <div class="js-clear-filters">Clear</div> - <div>Filter by</div> - <div class="js-close-filters">Close</div> - </div> - - <form action="" method="get" class="form form--filters"> - <ul class="form__filters select2 js-filter-list"> - {{ filter.form.as_ul }} - <li> - <button class="button button--primary" type="submit" value="Filter">Filter</button> - </li> - </ul> - </form> - </div> - {% endif %} - - {% render_table table %} + {% block table %} + {{ block.super }} + {% endblock %} </div> <a href="#" class="js-open-feed link link--open-feed"> <h4 class="heading heading--no-margin heading--activity-feed">Activity Feed</h4> </a> - {% include "funds/includes/activity-feed.html" %} {% endblock %} {% block extra_js %} - {{ filter.form.media.js }} - <script src="{% static 'js/apply/tabs.js' %}"></script> - <script src="{% static 'js/apply/all-submissions-table.js' %}"></script> - <script src="{% static 'js/apply/submission-filters.js' %}"></script> - <script src="{% static 'js/apply/submission-tooltips.js' %}"></script> + {{ block.super }} <script src="{% static 'js/apply/activity-feed.js' %}"></script> {% endblock %} diff --git a/opentech/apply/funds/templates/funds/submissions_by_round.html b/opentech/apply/funds/templates/funds/submissions_by_round.html index 30324a16596f2200fd6a3c983fe4d8bbf9f845dd..23063f307002ed2774cc8e8f7137c3156b6552a8 100644 --- a/opentech/apply/funds/templates/funds/submissions_by_round.html +++ b/opentech/apply/funds/templates/funds/submissions_by_round.html @@ -1,4 +1,4 @@ -{% extends "base-apply.html" %} +{% extends "funds/base_submissions_table.html" %} {% load render_bundle from webpack_loader %} {% block title %}{{ object }}{% endblock %} @@ -16,7 +16,9 @@ <div class="wrapper wrapper--large"> <div id="react-app"> - <h2>THERE WILL BE A TABLE HERE</h2> + {% block table %} + {{ block.super }} + {% endblock %} </div> </div> diff --git a/opentech/apply/funds/tests/test_views.py b/opentech/apply/funds/tests/test_views.py index 2b1cc1c3bb748f1be73dc47e1dda271fb2b0f6ed..f0209aa6a70bfbed444fb0b919e88c4531a27d11 100644 --- a/opentech/apply/funds/tests/test_views.py +++ b/opentech/apply/funds/tests/test_views.py @@ -556,7 +556,10 @@ class ByRoundTestCase(BaseViewTestCase): base_view_name = 'by_round' def get_kwargs(self, instance): - return {'pk': instance.id} + try: + return {'pk': instance.id} + except AttributeError: + return {'pk': instance['id']} class TestStaffSubmissionByRound(ByRoundTestCase): @@ -578,6 +581,10 @@ class TestStaffSubmissionByRound(ByRoundTestCase): response = self.get_page(page) self.assertEqual(response.status_code, 404) + def test_cant_access_non_existing_page(self): + response = self.get_page({'id': 555}) + self.assertEqual(response.status_code, 404) + class TestApplicantSubmissionByRound(ByRoundTestCase): user_factory = UserFactory @@ -597,3 +604,7 @@ class TestApplicantSubmissionByRound(ByRoundTestCase): page = new_round.get_site().root_page response = self.get_page(page) self.assertEqual(response.status_code, 403) + + def test_cant_access_non_existing_page(self): + response = self.get_page({'id': 555}) + self.assertEqual(response.status_code, 403) diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py index b96a1fbe15c7f7ded3bf76e94318c92c5d5e7017..15f46128b7632b1977f458d5df2503722e7650b6 100644 --- a/opentech/apply/funds/views.py +++ b/opentech/apply/funds/views.py @@ -3,6 +3,7 @@ from copy import copy from django.contrib.auth.decorators import login_required from django.contrib import messages from django.core.exceptions import PermissionDenied +from django.db.models import Q from django.http import HttpResponseRedirect, Http404 from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy @@ -36,12 +37,26 @@ from .workflow import STAGE_CHANGE_ACTIONS @method_decorator(staff_required, name='dispatch') -class SubmissionListView(AllActivityContextMixin, SingleTableMixin, FilterView): - template_name = 'funds/submissions.html' +class BaseAdminSubmissionsTable(SingleTableMixin, FilterView): table_class = AdminSubmissionsTable - filterset_class = SubmissionFilter + excluded_fields = [] + + @property + def excluded(self): + return { + 'exclude': self.excluded_fields + } + + def get_table_kwargs(self): + return self.excluded + + def get_filterset_kwargs(self, filterset_class): + kwargs = super().get_filterset_kwargs(filterset_class) + kwargs.update(self.excluded) + return kwargs + def get_queryset(self): return self.filterset_class._meta.model.objects.current().for_table(self.request.user) @@ -50,28 +65,51 @@ class SubmissionListView(AllActivityContextMixin, SingleTableMixin, FilterView): return super().get_context_data(active_filters=active_filters, **kwargs) +class SubmissionListView(AllActivityContextMixin, BaseAdminSubmissionsTable): + template_name = 'funds/submissions.html' + + +class SubmissionsByRound(BaseAdminSubmissionsTable): + template_name = 'funds/submissions_by_round.html' + + excluded_fields = ('round', 'lead', 'fund') + + def get_queryset(self): + # We want to only show lab or Rounds in this view, their base class is Page + try: + self.obj = Page.objects.get(pk=self.kwargs.get('pk')).specific + except Page.DoesNotExist: + raise Http404(_("No Round or Lab found matching the query")) + + if not isinstance(self.obj, (LabBase, RoundBase)): + raise Http404(_("No Round or Lab found matching the query")) + return super().get_queryset().filter(Q(round=self.obj) | Q(page=self.obj)) + + def get_context_data(self, **kwargs): + return super().get_context_data(object=self.obj, **kwargs) + + @method_decorator(staff_required, name='dispatch') -class SubmissionSearchView(SingleTableMixin, FilterView): +class SubmissionSearchView(BaseAdminSubmissionsTable): template_name = 'funds/submissions_search.html' - table_class = AdminSubmissionsTable filterset_class = SubmissionFilterAndSearch - def get_queryset(self): - return self.filterset_class._meta.model.objects.current().for_table(self.request.user) - def get_context_data(self, **kwargs): + kwargs = super().get_context_data(**kwargs,) + search_term = self.request.GET.get('query') # We have more data than just 'query' active_filters = len(self.filterset.data) > 1 - return super().get_context_data( + kwargs.update( search_term=search_term, active_filters=active_filters, - **kwargs, ) + return kwargs + @method_decorator(staff_required, name='dispatch') class ProgressSubmissionView(DelegatedViewMixin, UpdateView): @@ -420,17 +458,3 @@ class RevisionCompareView(DetailView): to_revision = self.object.revisions.get(id=self.kwargs['to']) self.compare_revisions(from_revision, to_revision) return super().get_context_data(**kwargs) - - -@method_decorator(staff_required, name='dispatch') -class SubmissionsByRound(DetailView): - model = Page - template_name = 'funds/submissions_by_round.html' - - def get_object(self): - # We want to only show lab or Rounds in this view, their base class is Page - obj = super().get_object() - obj = obj.specific - if not isinstance(obj, (LabBase, RoundBase)): - raise Http404(_("No Round or Lab found matching the query")) - return obj diff --git a/opentech/static_src/src/app/src/App.js b/opentech/static_src/src/app/src/App.js index 6704bbda9de764f34f2fe86880f136bae946e9bd..bb149517da239555433b6b2946174aa6cbdb7d7b 100644 --- a/opentech/static_src/src/app/src/App.js +++ b/opentech/static_src/src/app/src/App.js @@ -12,21 +12,26 @@ class App extends React.Component { } } - detailOpen = (state) => {this.setState({detailOpen: state})} + detailOpen = (state) => { + this.setState({style: {display: 'None'}}) + this.setState({detailOpen: true}) + } + + detailClose = () => { + this.setState({style: {}}) + this.setState({detailOpen: false}) + } render () { return ( <div> <div> - <button className="red-button" onClick={() => this.detailOpen(true)}>Detail View</button> + <button className="red-button" onClick={this.detailOpen}>Detail View</button> | - <button onClick={() => this.detailOpen(false)}>List View</button> + <button onClick={this.detailClose}>List View</button> </div> - {this.state.detailOpen ? ( - <div><h2>THIS IS REACT</h2></div> - ) : ( - <div dangerouslySetInnerHTML={ {__html: this.props.originalContent} } /> - )} + {this.state.detailOpen && <div><h2>THIS IS REACT</h2></div>} + <div style={this.state.style} dangerouslySetInnerHTML={ {__html: this.props.originalContent} } /> </div> )} }