diff --git a/django/econsensus/custom_organizations/forms.py b/django/econsensus/custom_organizations/forms.py index 1d8a56a93f35d7a33932f0fbd7f840216ae50209..0935371a178ed811ac59b6e0db5aed82723be12b 100644 --- a/django/econsensus/custom_organizations/forms.py +++ b/django/econsensus/custom_organizations/forms.py @@ -1,15 +1,19 @@ from django import forms -from organizations.models import Organization, get_user_model +from organizations.models import Organization, get_user_model, OrganizationUser from organizations.utils import create_organization from organizations.backends.forms import UserRegistrationForm from organizations.forms import OrganizationUserForm, OrganizationUserAddForm +from custom_organizations.models import Group from guardian.shortcuts import assign_perm, remove_perm from registration.forms import RegistrationFormUniqueEmail from registration.signals import user_registered from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ +from organizations.mixins import AdminRequiredMixin, \ + OrganizationMixin + # Subclass the default Registration form so we can add extra fields # to the sign up process (first_name and last_name) @@ -100,3 +104,32 @@ class CustomOrganizationUserAddForm(OrganizationUserAddForm): else: remove_perm('edit_decisions_feedback', self.instance.user, self.instance.organization) return self.instance + +class GroupAddForm(forms.ModelForm): + def __init__(self, request, organization, *args, **kwargs): + self.request = request + self.organization = organization + super(GroupAddForm, self).__init__(*args, **kwargs) + + class Meta: + model = Group + exclude = ('organization', 'members', 'owner') + + def save(self, **kwargs): + group_creator = OrganizationUser.objects.get(organization = self.organization, + user = self.request.user) + group = Group.objects.create(name=self.cleaned_data['name'], + organization = self.organization, owner=group_creator) + group.save() + group.members.add(group_creator) + return group + +class GroupJoinForm(forms.ModelForm): + def __init__(self, org_user, group, *args, **kwargs): + self.org_user = org_user + self.group = group + super(GroupJoinForm, self).__init__(*args, **kwargs) + + def save(self, **kwargs): + self.group.members.add(self.org_user) + return self.group \ No newline at end of file diff --git a/django/econsensus/custom_organizations/mixins.py b/django/econsensus/custom_organizations/mixins.py new file mode 100644 index 0000000000000000000000000000000000000000..fd1ac3998692499285fd49dd3beb51d0895ee34f --- /dev/null +++ b/django/econsensus/custom_organizations/mixins.py @@ -0,0 +1,14 @@ +from django.shortcuts import get_object_or_404 + +from custom_organizations.models import Group + +class GroupMixin(object): + group_model = Group + group_context_name = 'group' + + def get_object(self): + if hasattr(self, 'group'): + return self.group + group_pk = self.kwargs.get('group_pk', None) + return get_object_or_404(self.group_model, pk = group_pk) + get_group = get_object diff --git a/django/econsensus/custom_organizations/models.py b/django/econsensus/custom_organizations/models.py new file mode 100644 index 0000000000000000000000000000000000000000..6286e1e3b773c7a433ac10176633daf35bc605e5 --- /dev/null +++ b/django/econsensus/custom_organizations/models.py @@ -0,0 +1,22 @@ +from django import forms +from django.db import models + +from organizations.models import OrganizationUser, Organization +from django.utils.translation import ugettext_lazy as _ + +class Group(models.Model): + name = models.CharField(max_length=200, + help_text=_("The name of the Group")) + organization = models.ForeignKey(Organization, + related_name="organization_groups") + members = models.ManyToManyField(OrganizationUser) + owner = models.ForeignKey(OrganizationUser, + related_name="organizationuser_groupowner") + + class Meta: + ordering = ['name'] + verbose_name = _("group") + verbose_name_plural = _("groups") + + def __unicode__(self): + return self.name diff --git a/django/econsensus/custom_organizations/templatetags/__init__.py b/django/econsensus/custom_organizations/templatetags/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/django/econsensus/custom_organizations/templatetags/custom_org_filters.py b/django/econsensus/custom_organizations/templatetags/custom_org_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..b496e512cb6a064276bf9684001cb621d6b4fbc1 --- /dev/null +++ b/django/econsensus/custom_organizations/templatetags/custom_org_filters.py @@ -0,0 +1,17 @@ +from django import template +from custom_organizations.models import Group + +register = template.Library() + +@register.filter +def get_organization_groups_for_current_user(org, user): + return Group.objects.filter(organization=org, members__user=user) + +@register.filter +def is_member_of_group(group, user): + result = Group.objects.filter(id=group.id, members__user=user) + return result + +@register.filter +def get_organization_groups(org): + return Group.objects.filter(organization=org) \ No newline at end of file diff --git a/django/econsensus/custom_organizations/urls.py b/django/econsensus/custom_organizations/urls.py index 1e307118e7565c036b4ce91a814599f8e7910654..fbc98d1e634b6217b48e9baa8f67fc7e890bda4c 100644 --- a/django/econsensus/custom_organizations/urls.py +++ b/django/econsensus/custom_organizations/urls.py @@ -1,6 +1,9 @@ from django.conf.urls.defaults import patterns, include, url from django.contrib.auth.decorators import login_required -from views import (OrganizationAdminView, +from views import (OrganizationAdminView, + GroupCreate, + GroupDetailView, + GroupJoinView, CustomOrganizationCreate, CustomOrganizationDetail, CustomOrganizationUpdate, @@ -24,6 +27,15 @@ urlpatterns = patterns('', url(r'^(?P<organization_pk>[\d]+)/admin/$', view=login_required(OrganizationAdminView.as_view()), name="organization_admin"), + url(r'^(?P<organization_pk>[\d]+)/groups/add/$', + view=login_required(GroupCreate.as_view()), + name="create_group"), + url(r'^(?P<organization_pk>[\d]+)/groups/(?P<group_pk>[\d]+)/details/$', + view=login_required(GroupDetailView.as_view()), + name="group_detail"), + url(r'^(?P<organization_pk>[\d]+)/groups/(?P<group_pk>[\d]+)/join/$', + view=login_required(GroupJoinView.as_view()), + name="join_group"), url(r'^(?P<organization_pk>[\d]+)/people/add/$', view=login_required(CustomOrganizationUserCreate.as_view()), name="organization_user_add"), diff --git a/django/econsensus/custom_organizations/views.py b/django/econsensus/custom_organizations/views.py index b404e8fd4ceee97fc27e5702361b62281312f67b..2419a7672e2370c461d4134df479a88ba7418952 100644 --- a/django/econsensus/custom_organizations/views.py +++ b/django/econsensus/custom_organizations/views.py @@ -1,6 +1,7 @@ from django.contrib.sites.models import get_current_site from django.core.urlresolvers import reverse from django.shortcuts import redirect, get_object_or_404 +from django.views.generic import CreateView, DetailView, UpdateView from guardian.shortcuts import remove_perm @@ -15,20 +16,65 @@ from organizations.views import OrganizationCreate,\ OrganizationUserList, \ BaseOrganizationUserDelete, \ BaseOrganizationDetail -from organizations.mixins import AdminRequiredMixin +from organizations.mixins import AdminRequiredMixin, \ + OrganizationMixin, \ + OrganizationUserMixin +from custom_organizations.mixins import GroupMixin from custom_organizations.forms import CustomOrganizationForm,\ CustomOrganizationAddForm,\ CustomOrganizationUserForm,\ - CustomOrganizationUserAddForm + CustomOrganizationUserAddForm, \ + GroupAddForm, \ + GroupJoinForm from django.http import Http404 -from organizations.models import Organization +from organizations.models import Organization, OrganizationUser from publicweb.models import Feedback +from custom_organizations.models import Group class OrganizationAdminView(BaseOrganizationDetail): model = Organization template_name = 'organizations/organization_admin.html' +class GroupCreate(AdminRequiredMixin, OrganizationMixin, CreateView): + form_class = GroupAddForm + template_name = 'organizations/organizationgroup_form.html' + + def get_success_url(self): + return reverse("organization_list") + + def get_form_kwargs(self): + kwargs = super(GroupCreate, self).get_form_kwargs() + kwargs.update({'organization': self.get_organization(), + 'request': self.request}) + return kwargs + +class GroupDetailView(GroupMixin, DetailView): + model = Group + template_name = 'organizations/organizationgroup_detail.html' + + def get_context_data(self, **kwargs): + context = super(GroupDetailView, self).get_context_data(**kwargs) + return context + +class GroupJoinView(GroupMixin, UpdateView): + model = Group + form_class = GroupJoinForm + template_name = 'organizations/organizationgroup_join.html' + + def get_success_url(self): + return reverse("organization_list") + + def get_form_kwargs(self): + kwargs = super(GroupJoinView, self).get_form_kwargs() + self.group = self.get_group() + kwargs.update({'org_user': OrganizationUser.objects.get( + organization = self.group.organization, + user = self.request.user), + 'group': self.group}) + return kwargs + + class CustomOrganizationCreate(OrganizationCreate): form_class = CustomOrganizationAddForm @@ -77,13 +123,13 @@ class CustomOrganizationUserDelete(BaseOrganizationUserDelete): def _is_admin(self, request, organization_pk): organization = get_object_or_404(Organization, pk=organization_pk) return organization.is_admin(request.user) or request.user.is_superuser - + def _is_current_user(self, request, user_pk): """ Checks the user being accessed is the one currently logged in """ return request.user.id == int(user_pk) - + def dispatch(self, request, *args, **kwargs): organization_pk = kwargs.get('organization_pk', None) user_pk = kwargs.get('user_pk', None) diff --git a/django/econsensus/publicweb/static/css/styles.css b/django/econsensus/publicweb/static/css/styles.css index 8f6c952190e4427e7255104737510bd3dfa5a456..07ffe6f2a2a95667bf08dd608d1fab9f8299237e 100644 --- a/django/econsensus/publicweb/static/css/styles.css +++ b/django/econsensus/publicweb/static/css/styles.css @@ -312,10 +312,16 @@ input { margin-left:1em; } -#content ul.org_list ul li { - display:inline; - border-left:1px solid #666; - padding:0 0.5em; +#your-organizations ul.group_list { + text-indent:70px; +} + +#your-organizations li.group_list_member { + font-weight: bold; +} + +#your-organizations h3.groups_title { + text-indent:60px; } #content ul.org_list ul li:first-child { @@ -324,6 +330,12 @@ input { font-size: 1.4em; } +#content ul.org_list ul li { + display:inline; + border-left:1px solid #666; + padding:0 0.5em; +} + .page_title.actionitem, .page_title.feedback { display: block; diff --git a/django/econsensus/publicweb/templates/organizations/organization_admin.html b/django/econsensus/publicweb/templates/organizations/organization_admin.html index 3fa24368ef17bee90bb0d4ca0b776ea485894703..9a9459f37669df13047e32baa59ddbd0bab82206 100644 --- a/django/econsensus/publicweb/templates/organizations/organization_admin.html +++ b/django/econsensus/publicweb/templates/organizations/organization_admin.html @@ -22,6 +22,9 @@ <li> <a href="{% url 'organization_user_list' organization.pk %}">{% trans "Manage Members" %}</a> </li> + <li> + <a href="{% url 'create_group' organization.pk %}">{% trans "Create Group" %}</a> + </li> {% endif %} {% if organization|is_owner:user %} <li> @@ -29,5 +32,12 @@ </li> {% endif %} </ul> + +<ul> +<br /> +<br /> + <li><a href="{% url 'organization_list' %}">Back to your organizations</a> </li> +</ul> + </div> {% endblock %} \ No newline at end of file diff --git a/django/econsensus/publicweb/templates/organizations/organization_list.html b/django/econsensus/publicweb/templates/organizations/organization_list.html index 98449244cbb2273e30e0601b84f9c0a558e6c413..b17e469fedf1260bcdbc201de6823e3f43c6e3db 100644 --- a/django/econsensus/publicweb/templates/organizations/organization_list.html +++ b/django/econsensus/publicweb/templates/organizations/organization_list.html @@ -3,6 +3,7 @@ {% load i18n %} {% load org_tags %} {% load org_filters %} +{% load custom_org_filters %} {% block title %} {% trans "Your Organizations" %} @@ -15,7 +16,7 @@ {% block main_content %} <div id="your-organizations"> {% for organization in organizations %} -{% if forloop.first %}<ul class="org_list">{% endif %} +<ul class="org_list"> <li> <ul> <li> @@ -31,7 +32,22 @@ </li> </ul> </li> -{% if forloop.last %}</ul>{% endif %} +</ul> +<h3 class = "groups_title">Subgroups:</h3> +<ul class="group_list"> + {% for group in organization|get_organization_groups %} + {% if group|is_member_of_group:user %} + <li class="group_list_member"> + <a href="{% url 'group_detail' organization.pk group.pk %}"> {{ group }}</a> + {% else %} + <li> + {{ group }} <a href="{% url 'join_group' organization.pk group.pk %}">Join</a> + {% endif %} + {% empty %} + <li> <p> There are no subgroups for this organization. </p> </li> + </li> + {% endfor %} +</ul> {% empty %} <p>{% trans "You do not currently belong to any organizations."%}</p> <p>{% trans "You can only view and create discussions, proposals and decisions when you belong to an organization so try one of the following ..."%}</p> diff --git a/django/econsensus/publicweb/templates/organizations/organizationgroup_detail.html b/django/econsensus/publicweb/templates/organizations/organizationgroup_detail.html new file mode 100644 index 0000000000000000000000000000000000000000..06a1c059a4b3c96874908ab45fe4e98c508cb29a --- /dev/null +++ b/django/econsensus/publicweb/templates/organizations/organizationgroup_detail.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% load url from future %} +{% load i18n %} + +{% block title %} +Group Detail +{% endblock %} + +{% block heading %} +Group Detail for {{ object.name }} +{% endblock %} + +{% block main_content %} +<h3> Group Members </h3> +<ul> +{% for member in object.members.all %} + <li> {{ member.user.username }} </li> +</ul> +{% empty %} +<p>> {% trans "This group currently has no members" %} +{% endfor %} +{% endblock %} diff --git a/django/econsensus/publicweb/templates/organizations/organizationgroup_form.html b/django/econsensus/publicweb/templates/organizations/organizationgroup_form.html new file mode 100644 index 0000000000000000000000000000000000000000..54c8d2951f1992741645efc7566b3d4276f2de0e --- /dev/null +++ b/django/econsensus/publicweb/templates/organizations/organizationgroup_form.html @@ -0,0 +1,32 @@ +{% extends "base_org_admin.html" %} +{% load url from future %} + +{% load i18n %} + +{% block title %} +Create group for organization +{% endblock %} + +{% block heading %} +{% trans "Create group" %} +{% endblock %} + +{% block main_content %} + +<p> {% blocktrans %} Create a new working group within {{ organization }}. You will <br /> automatically be made the owner of the group.{% endblocktrans %} </p> + +<br /> + +<form action="." method="post">{% csrf_token %} + {% for field in form %} + {{ field.errors }} + {{ field }} + {% endfor %} + <br /> + <br /> + <input class="loose button" type="submit" value="OK"> +</form> +<form method="GET" action="{% url 'organization_admin' organization.pk %}"> + <input class="loose button" type="submit" value="Cancel"> +</form> +{% endblock %} \ No newline at end of file diff --git a/django/econsensus/publicweb/templates/organizations/organizationgroup_join.html b/django/econsensus/publicweb/templates/organizations/organizationgroup_join.html new file mode 100644 index 0000000000000000000000000000000000000000..3262209976e53af6e730954a80930306eaab516f --- /dev/null +++ b/django/econsensus/publicweb/templates/organizations/organizationgroup_join.html @@ -0,0 +1,23 @@ +{% extends "base_org_admin.html" %} +{% load url from future %} +{% load i18n %} + +{% block title %} +Join group +{% endblock %} + +{% block heading %} +Request to join group? +{% endblock %} + +{% block main_content %} +<p> Would you like to request to join this group? </p> +<form action="." method="post">{% csrf_token %} + <input class="loose button" type="submit" value="Send Request"> +</form> +<form method="GET" action="{% url 'organization_list' %}"> + <input class="loose button" type="submit" value="Cancel"> +</form> + + +{% endblock %} diff --git a/django/econsensus/publicweb/tests/custom_organizations/forms_test.py b/django/econsensus/publicweb/tests/custom_organizations/forms_test.py index c487052452992ac5d316d70196e04c1ec777791f..c66fc95333e609eb954d24c348415c6c39a1ded9 100644 --- a/django/econsensus/publicweb/tests/custom_organizations/forms_test.py +++ b/django/econsensus/publicweb/tests/custom_organizations/forms_test.py @@ -1,11 +1,12 @@ from django.test import TestCase from django.test.client import RequestFactory from publicweb.tests.factories import UserFactory, \ - OrganizationOwnerFactory, OrganizationFactory + OrganizationOwnerFactory, OrganizationFactory, \ + OrganizationUserFactory from custom_organizations.forms import CustomOrganizationAddForm,\ CustomOrganizationUserForm, CustomOrganizationUserAddForm, \ - CustomOrganizationForm + CustomOrganizationForm, GroupAddForm from django.template.defaultfilters import slugify @@ -13,6 +14,17 @@ from guardian.shortcuts import assign_perm from django.forms.fields import BooleanField GUARDIAN_PERMISSION = 'edit_decisions_feedback' +class TestGroupAddForm(TestCase): + + def test_creator_added_as_member(self): + org_user = OrganizationUserFactory() + request = RequestFactory() + request.user = org_user.user + form = GroupAddForm(request, org_user.organization) + form.cleaned_data = {'name' : 'Testgroup'} + group = form.save() + self.assertEqual(group.members.get(id = org_user.id), org_user) + class TestCustomOrganizationForm(TestCase):