Skip to content

Commit

Permalink
Merge pull request #3841 from akvo/issue/3839-dropdown-custom-fields
Browse files Browse the repository at this point in the history
[#3839] Support dropdown type for custom fields in the DB
  • Loading branch information
punchagan authored Nov 14, 2019
2 parents 7707cd3 + c3bdbbd commit 1fa31ce
Show file tree
Hide file tree
Showing 9 changed files with 1,029 additions and 64 deletions.
63 changes: 57 additions & 6 deletions akvo/rest/views/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
ProjectDirectorySerializer,
TypeaheadOrganisationSerializer,
TypeaheadSectorSerializer,
ProjectMetadataSerializer,)
ProjectMetadataSerializer,
OrganisationCustomFieldSerializer,)
from akvo.rest.views.utils import (
int_or_none, get_cached_data, get_qs_elements_for_page, set_cached_data
)
from akvo.rsr.models import Project
from akvo.rsr.models import Project, OrganisationCustomField
from akvo.rsr.filters import location_choices, get_m49_filter
from akvo.rsr.views.my_rsr import user_editable_projects
from akvo.utils import codelist_choices
Expand Down Expand Up @@ -242,13 +243,19 @@ def project_directory(request):
page = request.rsr_page
projects = page.projects() if page else Project.objects.all().public().published()

# Exclude projects which don't have an image or a title
# FIXME: This happens silently and may be confusing?
projects = projects.exclude(Q(title='') | Q(current_image=''))
if not page:
# Exclude projects which don't have an image or a title for RSR site
projects = projects.exclude(Q(title='') | Q(current_image=''))
else:
# On partner sites, all projects show up. Partners are expected to fix
# their data to fix their pages!
pass

# Filter projects based on query parameters
filter_, text_filter = _create_filters_query(request)
projects = projects.filter(filter_).distinct() if filter_ is not None else projects
projects = _filter_by_custom_fields(request, projects)

# NOTE: The text filter is handled differently/separately from the other filters.
# The text filter allows users to enter free form text, which could result in no
# projects being found for the given text. Other fields only allow selecting from
Expand Down Expand Up @@ -309,13 +316,19 @@ def project_directory(request):
cached_organisations, _ = get_cached_data(
request, 'organisations', organisations, TypeaheadOrganisationSerializer
)

custom_fields = (
OrganisationCustomField.objects.filter(type='dropdown',
organisation=page.organisation,
show_in_searchbar=True)
if page else []
)
response = {
'project_count': count,
'projects': cached_projects,
'showing_cached_projects': showing_cached_projects,
'organisation': cached_organisations,
'sector': TypeaheadSectorSerializer(sectors, many=True).data,
'custom_fields': OrganisationCustomFieldSerializer(custom_fields, many=True).data,
'location': cached_locations,
'page_size_default': settings.PROJECT_DIRECTORY_PAGE_SIZES[0],
}
Expand Down Expand Up @@ -352,3 +365,41 @@ def _create_filters_query(request):
]
filters = filter(None, all_filters)
return reduce(lambda x, y: x & y, filters) if filters else None, title_or_subtitle_filter


def _filter_by_custom_fields(request, projects):
for custom_field_query in request.GET:
if not custom_field_query.startswith('custom_field__'):
continue

value = request.GET.get(custom_field_query)
try:
org_custom_field_id = int(custom_field_query.split('__', 1)[-1])
org_custom_field = OrganisationCustomField.objects.get(pk=org_custom_field_id)
except (OrganisationCustomField.DoesNotExist, ValueError):
continue

if org_custom_field.type != 'dropdown':
filter_ = Q(custom_fields__name=org_custom_field.name, custom_fields__value=value)
else:
dropdown_options = org_custom_field.dropdown_options['options']
selection = _get_selection(dropdown_options, value)
filter_ = Q(custom_fields__name=org_custom_field.name,
custom_fields__dropdown_selection__contains=selection)
projects = projects.filter(filter_)

return projects


def _get_selection(options, value):
if isinstance(value, basestring):
print(value)
indexes = map(int, value.split('__'))
else:
indexes = value

selection = options[indexes[0]]
sub_options = selection.pop('options', [])
if sub_options and indexes[1:]:
selection['options'] = _get_selection(sub_options, indexes[1:])
return [selection]
6 changes: 5 additions & 1 deletion akvo/rsr/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,12 @@ def get_extra(self, request, obj=None, **kwargs):

class OrganisationCustomFieldInline(NestedTabularInline):
model = apps.get_model('rsr', 'organisationcustomfield')
fields = ('name', 'type', 'section', 'order', 'max_characters', 'mandatory', 'help_text')
fields = ('name', 'type', 'section', 'order', 'max_characters', 'mandatory', 'help_text',
'dropdown_options', 'show_in_searchbar')
fk_name = 'organisation'
formfield_overrides = {
JSONField: {'widget': PrettyJSONWidget}
}

def get_extra(self, request, obj=None, **kwargs):
if obj:
Expand Down
55 changes: 51 additions & 4 deletions akvo/rsr/front-end/scripts-src/classic/jsx/directory-utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ var ProjectDirectory = React.createClass({
allProjects = this.props.i18n.all_projects_text,
allUpdates = this.props.i18n.all_updates_text;


return (
<section className="main-list projects">
<div className="container-fluid">
Expand Down Expand Up @@ -253,8 +252,8 @@ var ProjectDirectory = React.createClass({
>
<p className="text-center listMsg">{listMsg}</p>
<p className="submenu-footer-stead">
<a href="/en/projects">{allProjects}</a>
<a href="/en/updates">{allUpdates}</a>
<a href="/en/projects">{allProjects}</a>
<a href="/en/updates">{allUpdates}</a>
</p>
<ul className="projectListUl group">
{this.props.projects.map(function(project) {
Expand Down Expand Up @@ -468,6 +467,52 @@ var SearchBar = React.createClass({
/>
);
};
var create_custom_filter_options = function(custom_filter) {
let options = [];
const create_item = (option, id, parent_item) => {
const level = parent_item === undefined ? 0 : parent_item.level + 1;
const item = {
label: option.name,
filterBy:
parent_item === undefined
? option.name
: `${parent_item.filterBy} {option.name}`,
id: parent_item === undefined ? id : `${parent_item.id}__${id}`,
level: level,
indent: level * 4
};
options.push(item);
if (option.options) {
option.options.map((sub_option, sub_id) => {
create_item(sub_option, sub_id, item);
});
}
return item;
};

custom_filter.dropdown_options.options.forEach((option, index) => {
create_item(option, index);
});
return options;
};
var create_custom_filter = function(custom_filter) {
const display_name = custom_filter.name;
const filter_name = `custom_field__${custom_filter.id}`;
const options = create_custom_filter_options(custom_filter);
console.log(options);
return (
<Filter
ref={filter_name}
key={filter_name}
options={options}
name={filter_name}
display_name={display_name}
selected={[]}
onChange={this.props.onChange}
disabled={this.props.disabled}
/>
);
};
var reset_button = _.isEmpty(
_.pick(this.props.selected, "location", "organisation", "sector", "title_or_subtitle")
) ? (
Expand All @@ -494,6 +539,7 @@ var SearchBar = React.createClass({
<div id="filter-wrapper col-sm-12 col-lg-6 col-md-6 col-lg-offset-2 col-md-offset-2">
<div>
{this.props.filters.map(create_filter, this)}
{this.props.options.custom_fields.map(create_custom_filter, this)}
{reset_button}
</div>
</div>
Expand All @@ -510,7 +556,8 @@ var App = React.createClass({
keyword: [],
location: [],
organisation: [],
sector: []
sector: [],
custom_fields: []
};
var urlState = this.getStateFromUrl();
var state = {
Expand Down
Loading

0 comments on commit 1fa31ce

Please sign in to comment.