Skip to content

Commit

Permalink
feat: add a tool flow (resolves #37) (#119)
Browse files Browse the repository at this point in the history
* feat: add a tool, first pass (#37)

* feat: add a tool, second pass (#37)

* fix: enable another rule

* fix: add another ignore path

* feat: move labels to forms

* fix: restore labels to model

* fix: restore labels to model

* feat: niche handling, first pass

* feat: niche selection

* feat: finish tool flow

* fix: localize some tool choice strings, set defaults to empty

* feat: set Tool.submitted_by_email on creation
  • Loading branch information
greatislander authored Jun 30, 2020
1 parent f031b21 commit 4eb969e
Show file tree
Hide file tree
Showing 14 changed files with 387 additions and 13 deletions.
84 changes: 83 additions & 1 deletion maps/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from django.template.defaultfilters import safe
from django_countries.fields import CountryField
from accounts.models import Role, SocialNetwork, UserSocialNetwork
from mdi.models import Organization, Category, Language, OrganizationSocialNetwork, Stage, Type
from mdi.models import Organization, Category, Language, OrganizationSocialNetwork, Stage, Tool, Type, Pricing, License


class BaseForm(forms.Form):
error_css_class = 'error'
Expand All @@ -28,12 +29,14 @@ def __init__(self, *args, **kwargs):

super(BaseModelForm, self).__init__(*args, **kwargs)


class IndividualProfileDeleteForm(BaseModelForm):
class Meta:
model = get_user_model()
fields = ['has_profile']
widgets = {'has_profile': HiddenInput}


class IndividualBasicInfoForm(BaseModelForm):
first_name = CharField(
required=True,
Expand Down Expand Up @@ -64,6 +67,7 @@ class Meta:
'url': _('Website address')
}


class IndividualContactInfoForm(BaseModelForm):
city = CharField(
required=True,
Expand Down Expand Up @@ -103,6 +107,7 @@ def __init__(self, *args, **kwargs):
self.fields['lat'].value = self.lat
self.fields['lng'].value = self.lng


class IndividualRolesForm(BaseForm):
roles = forms.ModelMultipleChoiceField(
queryset=Role.objects.all(),
Expand Down Expand Up @@ -158,6 +163,8 @@ class Meta:
help_texts = {
'community_skills': _('Provide a short description.')
}


class IndividualDetailedInfoForm(BaseModelForm):
class Meta:
model = get_user_model()
Expand All @@ -174,6 +181,7 @@ class Meta:
'projects': _('List any current or past projects you would like to share with others.')
}


class IndividualSocialNetworkForm(BaseModelForm):
class Meta:
model = UserSocialNetwork
Expand All @@ -188,6 +196,7 @@ class Meta:

IndividualSocialNetworkFormSet = formset_factory(IndividualSocialNetworkForm, extra=0)


class OrganizationTypeForm(BaseForm):
type = forms.ModelChoiceField(
Type.objects.filter(name__in=[
Expand All @@ -202,6 +211,8 @@ class OrganizationTypeForm(BaseForm):
initial=0,
widget=RadioSelect(attrs={'class': 'input-group radio'})
)


class OrganizationBasicInfoForm(BaseModelForm):
languages = forms.ModelMultipleChoiceField(
queryset=Language.objects.all(),
Expand Down Expand Up @@ -307,6 +318,7 @@ def __init__(self, *args, **kwargs):
else:
self.fields['year_founded'].required = False


class OrganizationContactInfoForm(BaseModelForm):
city = CharField(
required=True,
Expand Down Expand Up @@ -336,6 +348,7 @@ class Meta:
'postal_code': _('ZIP or postal code')
}


class OrganizationDetailedInfoForm(BaseModelForm):
categories = forms.ModelMultipleChoiceField(
queryset=Category.objects.all(),
Expand Down Expand Up @@ -402,6 +415,7 @@ class Meta:
'sectors': _('Hold down the <kbd>ctrl</kbd> (Windows) or <kbd>command</kbd> (macOS) key to select multiple options.'),
}


class OrganizationScopeAndImpactForm(BaseModelForm):
geo_scope = forms.ChoiceField(
choices=[
Expand Down Expand Up @@ -432,6 +446,7 @@ class Meta:
'impacted_exact_number': _('Include clients and users as well as their family members or others indirectly impacted by the work of your co-operative.')
}


class OrganizationSocialNetworkForm(BaseModelForm):
class Meta:
model = OrganizationSocialNetwork
Expand All @@ -445,3 +460,70 @@ class Meta:


OrganizationSocialNetworkFormSet = formset_factory(OrganizationSocialNetworkForm, extra=0)


class ToolBasicInfoForm(BaseModelForm):
class Meta:
model = Tool
fields = [
'name',
'niches',
'description',
'url'
]
labels = {
'name': _('Name of tool'),
'niches': _('What is this tool used for?'),
'description': _('Description of tool'),
'url': _('URL of tool website')
}
help_texts = {
'description': _('Max 270 characters.')
}


class ToolDetailedInfoForm(BaseModelForm):
pricing = forms.ModelChoiceField(
queryset=Pricing.objects.all(),
empty_label=_('Not sure'),
required=False,
label=_('How much does this tool cost?'),
widget=RadioSelect(attrs={'class': 'input-group radio'})
)

license = forms.ModelChoiceField(
queryset=License.objects.all(),
empty_label=_('Not sure'),
required=False,
label=_('Please choose a specific free / libre / open source license')
)

sector = forms.ChoiceField(
choices=[('no', _('No')), ('yes', _('Yes'))],
required=True,
initial='no',
label=_('Is this tool for a specific sector or sectors?'),
widget=RadioSelect(attrs={'class': 'input-group radio'})
)

class Meta:
model = Tool
fields = [
'pricing',
'license_type',
'license',
'sector',
'sectors',
'languages_supported',
'coop_made'
]
labels = {
'license_type': _('How is this tool licensed?'),
'sectors': _('Please choose a sector or sectors:'),
'languages_supported': _('What languages does this tool support?'),
'coop_made': _('Was this tool created by a co-op?')
}
widgets = {
'license_type': RadioSelect(attrs={'class': 'input-group radio'}),
'coop_made': RadioSelect(attrs={'class': 'input-group radio'})
}
19 changes: 19 additions & 0 deletions maps/static/maps/css/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ select.multiple {
textarea + label,
select + label,
.helptext + label,
.input-group + label,
ul.checkbox + label,
ul.radio + label {
margin-top: rem(45);
Expand Down Expand Up @@ -519,3 +520,21 @@ h1 + .profile__meta {
}
}
}

.niches > li {
padding: rem(8) 0 rem(6);
position: relative;
}

.niches button {
position: absolute;
right: 0;
top: 0;
}

[for="id_detailed_info-license"],
#id_detailed_info-license,
[for="id_detailed_info-sectors"],
#id_detailed_info-sectors {
display: none;
}
49 changes: 49 additions & 0 deletions maps/static/maps/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,55 @@ if (basicInfo) {
new Pinecone.Card( card );
});

[...document.querySelectorAll( '.input-group__parent > li' )].forEach( (container) => {
const input = container.querySelector( '.input--parent' );
const subInputs = container.querySelectorAll( '.input-group__descendant input' );
if ( 0 < subInputs.length ) {
new Pinecone.NestedCheckbox( container, input, subInputs );
}
} );

[...document.querySelectorAll( '.filter-disclosure-label' )].forEach( (label) => {
new Pinecone.DisclosureButton( label, { buttonVariant: 'button--disc', visuallyHiddenLabel: true } );
} );

[...document.querySelectorAll('[name="detailed_info-license_type"')].forEach(element => {
element.addEventListener('change', () => {
const license = document.getElementById('id_detailed_info-license');
const licenseLabel = document.querySelector('[for="id_detailed_info-license"]');
if (element.value === 'floss' || element.value === 'proprietary-with-floss-integration-tools') {
license.style.display = 'block';
licenseLabel.style.display = 'block';
} else {
license.style.display = 'none';
licenseLabel.style.display = 'none';
}
});
});

[...document.querySelectorAll('[name="detailed_info-sector"')].forEach(element => {
element.addEventListener('change', () => {
const sectors = document.getElementById('id_detailed_info-sectors');
const sectorsLabel = document.querySelector('[for="id_detailed_info-sectors"]');
if (element.value === 'yes') {
sectors.style.display = 'block';
sectorsLabel.style.display = 'block';
} else {
sectors.style.display = 'none';
sectorsLabel.style.display = 'none';
}
});
});

[...document.querySelectorAll('[role="checkbox"]')].forEach(checkbox => {
checkbox.addEventListener('click', () => {
if (checkbox.getAttribute('aria-checked') !== 'true') {
const disclosureButton = checkbox.parentNode.querySelector('button');
disclosureButton.setAttribute('aria-expanded', 'true');
}
});
});

[...document.querySelectorAll('.delete-organization')].forEach((form) => {
form.addEventListener('submit', (event) => {
event.preventDefault();
Expand Down
66 changes: 66 additions & 0 deletions maps/templates/maps/profiles/tool/basic_info.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{% extends "maps/base.html" %}
{% load i18n %}
{% load maps_extras %}
{% block bodyclass %}form-wizard{% endblock %}
{% block title %}{{ 'Basic information'|titlify }}{% endblock %}}

{% block content %}
<form class="form" action="" method="post" novalidate>
<div class="page-header">
{% include 'maps/profiles/back.html' %}
<h1>{% trans 'Add a tool' %}</h1>
{% include 'maps/profiles/steps.html' %}
</div>
<div class="form__content">
{% if form.errors %}
{% include 'maps/profiles/errors.html' %}
{% endif %}
<h2>{% trans 'Basic information' %}</h2>
{{ wizard.management_form }}
{% csrf_token %}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form }}
{% endfor %}
{% else %}
{{ wizard.form.name.label_tag }}
{{ wizard.form.name }}
{{ wizard.form.niches.label_tag }}
<ul id="niches" class="input-group input-group__parent checkbox niches">
{% for parent, value in niche_dict.items %}
<li>
{% if value.children|length > 0%}
<label for="niche-{{ parent|slugify }}">
<input class="input--parent" id="niche-{{ parent|slugify }}" type="checkbox" name="basic_info-niche-parents" value="niche-{{ parent|slugify }}" />{{ parent }}
</label>
<span class="filter-disclosure-label" hidden>{% blocktrans with parent=parent count=value.children|length %}show {{ count }} subtypes for "{{ parent }}"{% endblocktrans %}</span>
<span class="supplementary-label" hidden>{% blocktrans with count=value.children|length %} (and {{ count }} subtopics){% endblocktrans %}</span>
<ul class="input-group checkbox input-group__descendant">
{% for child in value.children %}
<li>
<label for="niche-{{ child.id }}">
<input id="niche-{{ child.id }}" type="checkbox" name="basic_info-niches" value="{{ child.id }}" />{{ child.name|capfirst }}
</label>
</li>
{% endfor %}
</ul>
{% else %}
<label for="niche-{{ value.id }}">
<input class="input--parent" id="niche-{{ value.id }}" type="checkbox" name="basic_info-niches" value="{{ value.id }}" />{{ parent }}
</label>
{% endif %}
</li>
{% endfor %}
</ul>
{{ wizard.form.description.label_tag }}
{{ wizard.form.description }}
<span class="helptext">{{ wizard.form.description.help_text | safe }}</span>
{{ wizard.form.url.label_tag }}
{{ wizard.form.url }}
{% endif %}
{% include 'maps/profiles/footer.html' %}
</div>
</form>
{% include 'maps/profiles/cancel.html' %}
{% endblock %}
33 changes: 33 additions & 0 deletions maps/templates/maps/profiles/tool/detailed_info.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{% extends "maps/base.html" %}
{% load i18n %}
{% load maps_extras %}
{% block bodyclass %}form-wizard{% endblock %}
{% block title %}{{ 'Detailed information'|titlify }}{% endblock %}}

{% block content %}
<form class="form" action="" method="post" novalidate>
<div class="page-header">
{% include 'maps/profiles/back.html' %}
<h1>{% trans 'Add a tool' %}</h1>
{% include 'maps/profiles/steps.html' %}
</div>
<div class="form__content">
{% if form.errors %}
{% include 'maps/profiles/errors.html' %}
{% endif %}
<h2>{% trans 'Detailed information' %}</h2>
{{ wizard.management_form }}
{% csrf_token %}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form }}
{% endfor %}
{% else %}
{{ wizard.form }}
{% endif %}
{% include 'maps/profiles/footer.html' %}
</div>
</form>
{% include 'maps/profiles/cancel.html' %}
{% endblock %}
2 changes: 2 additions & 0 deletions maps/templatetags/maps_extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

register = template.Library()


@register.filter(name='titlify')
def titlify(value):
"""Prepends value and emdash to base title"""
title_base = 'Platform Co-op Directory'
return value + ' – ' + title_base


@register.inclusion_tag('maps/partials/icon.html')
def icon(name, **kwargs):
modifier = kwargs.get('modifier', name)
Expand Down
Loading

0 comments on commit 4eb969e

Please sign in to comment.