Commit 48e72b9f authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

Progress VII

* add policies during group creation
* improve performance (reduce db access)
parent 7585f252
...@@ -36,6 +36,7 @@ from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, IM_STATIC_URL, ...@@ -36,6 +36,7 @@ from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, IM_STATIC_URL,
GLOBAL_MESSAGES, PROFILE_EXTRA_LINKS GLOBAL_MESSAGES, PROFILE_EXTRA_LINKS
from astakos.im.api import get_menu from astakos.im.api import get_menu
from astakos.im.util import get_query from astakos.im.util import get_query
from astakos.im.models import GroupKind
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -79,3 +80,6 @@ def menu(request): ...@@ -79,3 +80,6 @@ def menu(request):
for item in menu_items: for item in menu_items:
item['is_active'] = absolute(request.path) == item['url'] item['is_active'] = absolute(request.path) == item['url']
return {'menu':menu_items} return {'menu':menu_items}
def group_kinds(request):
return {'group_kinds': GroupKind.objects.exclude(name='default').values_list('name', flat=True)}
\ No newline at end of file
...@@ -490,53 +490,61 @@ class ExtendedPasswordChangeForm(PasswordChangeForm): ...@@ -490,53 +490,61 @@ class ExtendedPasswordChangeForm(PasswordChangeForm):
user.save() user.save()
return user return user
def get_astakos_group_creation_form(request): class AstakosGroupCreationForm(forms.ModelForm):
class AstakosGroupCreationForm(forms.ModelForm): # issue_date = forms.DateField(widget=SelectDateWidget())
issue_date = forms.DateField(widget=SelectDateWidget(), initial=datetime.now()) # expiration_date = forms.DateField(widget=SelectDateWidget())
# TODO set initial in exact one month kind = forms.ModelChoiceField(
expiration_date = forms.DateField(widget=SelectDateWidget(), initial = datetime.now() + timedelta(days=30)) queryset=GroupKind.objects.all(),
kind = forms.ModelChoiceField(queryset=GroupKind.objects.all(), empty_label=None) label="",
name = forms.URLField() widget=forms.HiddenInput()
)
class Meta: name = forms.URLField()
model = AstakosGroup
def __init__(self, *args, **kwargs):
super(AstakosGroupCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['kind', 'name', 'desc', 'issue_date',
'expiration_date', 'estimated_participants',
'moderation_enabled']
def save(self, commit=True):
g = super(AstakosGroupCreationForm, self).save(commit=False)
if commit:
g.save()
g.owner = [request.user]
g.approve_member(request.user)
return g
return AstakosGroupCreationForm class Meta:
model = AstakosGroup
def get_astakos_group_policy_creation_form(astakosgroup):
class AstakosGroupPolicyCreationForm(forms.ModelForm):
choices = Resource.objects.filter(~Q(astakosgroup=astakosgroup))
resource = forms.ModelChoiceField(queryset=choices, empty_label=None)
# TODO check that it does not hit the db
group = forms.ModelChoiceField(queryset=AstakosGroup.objects.all(), initial=astakosgroup, widget=forms.HiddenInput())
class Meta:
model = AstakosGroupQuota
return AstakosGroupPolicyCreationForm def __init__(self, *args, **kwargs):
try:
resources = kwargs.pop('resources')
except KeyError:
resources = {}
super(AstakosGroupCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['kind', 'name', 'desc', 'issue_date',
'expiration_date', 'estimated_participants',
'moderation_enabled']
for id, r in resources.iteritems():
self.fields['resource_%s' % id] = forms.IntegerField(
label=r,
required=False,
help_text=_('Leave it blank for no additional quota.')
)
def resources(self):
for name, value in self.cleaned_data.items():
prefix, delimiter, suffix = name.partition('resource_')
if suffix:
# yield only those having a value
if not value:
continue
yield (suffix, value)
class AstakosGroupSearchForm(forms.Form): class AstakosGroupSearchForm(forms.Form):
q = forms.CharField(max_length=200, label='') q = forms.CharField(max_length=200, label='')
class MembershipCreationForm(forms.ModelForm): class MembershipCreationForm(forms.ModelForm):
# TODO check not to hit the db # TODO check not to hit the db
group = forms.ModelChoiceField(queryset=AstakosGroup.objects.all(), widget=forms.HiddenInput()) group = forms.ModelChoiceField(
person = forms.ModelChoiceField(queryset=AstakosUser.objects.all(), widget=forms.HiddenInput()) queryset=AstakosGroup.objects.all(),
date_requested = forms.DateField(widget=forms.HiddenInput(), input_formats="%d/%m/%Y") widget=forms.HiddenInput()
)
person = forms.ModelChoiceField(
queryset=AstakosUser.objects.all(),
widget=forms.HiddenInput()
)
date_requested = forms.DateField(
widget=forms.HiddenInput(),
input_formats="%d/%m/%Y"
)
class Meta: class Meta:
model = Membership model = Membership
......
...@@ -65,8 +65,8 @@ class Command(BaseCommand): ...@@ -65,8 +65,8 @@ class Command(BaseCommand):
if options.get('pending'): if options.get('pending'):
groups = filter(lambda g: g.is_disabled, groups) groups = filter(lambda g: g.is_disabled, groups)
labels = ('id', 'name', 'enabled', 'permissions') labels = ('id', 'name', 'enabled', 'moderation', 'permissions')
columns = (3, 12, 12, 50) columns = (3, 25, 12, 12, 50)
if not options.get('csv'): if not options.get('csv'):
line = ' '.join(l.rjust(w) for l, w in zip(labels, columns)) line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
...@@ -78,6 +78,7 @@ class Command(BaseCommand): ...@@ -78,6 +78,7 @@ class Command(BaseCommand):
fields = ( str(group.id), fields = ( str(group.id),
group.name, group.name,
format_bool(group.is_enabled), format_bool(group.is_enabled),
format_bool(group.moderation_enabled),
','.join(p.codename for p in group.permissions.all())) ','.join(p.codename for p in group.permissions.all()))
if options.get('csv'): if options.get('csv'):
......
...@@ -154,12 +154,10 @@ class AstakosGroup(Group): ...@@ -154,12 +154,10 @@ class AstakosGroup(Group):
self.save() self.save()
def approve_member(self, person): def approve_member(self, person):
try: m, created = self.membership_set.get_or_create(person=person)
self.membership_set.create(person=person, date_joined=datetime.now()) # update date_joined in any case
except IntegrityError: m.date_joined=datetime.now()
m = self.membership_set.get(person=person) m.save()
m.date_joined = datetime.now()
m.save()
def disapprove_member(self, person): def disapprove_member(self, person):
self.membership_set.remove(person=person) self.membership_set.remove(person=person)
...@@ -175,15 +173,24 @@ class AstakosGroup(Group): ...@@ -175,15 +173,24 @@ class AstakosGroup(Group):
@property @property
def quota(self): def quota(self):
d = {} d = defaultdict(int)
for q in self.astakosgroupquota_set.all(): for q in self.astakosgroupquota_set.all():
d[q.resource.name] = q.limit d[q.resource] += q.limit
return d return d
@property @property
def has_undefined_policies(self): def has_undefined_policies(self):
# TODO: can avoid query? # TODO: can avoid query?
return Resource.objects.filter(~Q(astakosgroup=self)).exists() return Resource.objects.filter(~Q(astakosgroup=self)).exists()
@property
def owners(self):
return self.owner.all()
@owners.setter
def owners(self, l):
self.owner = l
map(self.approve_member, l)
class AstakosUser(User): class AstakosUser(User):
""" """
...@@ -567,4 +574,8 @@ def superuser_post_save(sender, instance, **kwargs): ...@@ -567,4 +574,8 @@ def superuser_post_save(sender, instance, **kwargs):
if instance.is_superuser: if instance.is_superuser:
create_astakos_user(instance) create_astakos_user(instance)
post_save.connect(superuser_post_save, sender=User) post_save.connect(superuser_post_save, sender=User)
\ No newline at end of file
def get_resources():
# use cache
return Resource.objects.select_related().all()
\ No newline at end of file
...@@ -57,6 +57,7 @@ context_processors = [ ...@@ -57,6 +57,7 @@ context_processors = [
'astakos.im.context_processors.invitations', 'astakos.im.context_processors.invitations',
'astakos.im.context_processors.menu', 'astakos.im.context_processors.menu',
'astakos.im.context_processors.custom_messages', 'astakos.im.context_processors.custom_messages',
'astakos.im.context_processors.group_kinds',
'synnefo.lib.context_processors.cloudbar' 'synnefo.lib.context_processors.cloudbar'
] ]
......
...@@ -95,16 +95,5 @@ ...@@ -95,16 +95,5 @@
<p>No policies</p> <p>No policies</p>
{% endif %} {% endif %}
</div> </div>
{% if user in object.owner.all and more_policies %}
<div class="rightcol">
<form action="{% url group_policies_add object.id %}" method="post" class="innerlabels signup">{% csrf_token %}
<h2><span>NEW POLICY</span></h2>
{% include "im/form_render.html" %}
<div class="form-row submit">
<input type="submit" class="submit altcol" value="+" />
</div>
</form>
</div>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<h2><span>NEW GROUP</span></h2> <h2><span>NEW GROUP</span></h2>
{% include "im/form_render.html" %} {% include "im/form_render.html" %}
<div class="form-row submit"> <div class="form-row submit">
<input type="submit" class="submit altcol" value="ADD POLICIES" /> <input type="submit" class="submit altcol" value="SUBMIT" />
</div> </div>
</form> </form>
</div> </div>
......
...@@ -14,8 +14,10 @@ ...@@ -14,8 +14,10 @@
</form> </form>
{% else %} {% else %}
<p class="submit-rt"> <p class="submit-rt">
<a href="{% url group_add %}" class="submit">Create a group</a> {% for gname in group_kinds %}
<a href="{% url group_search %}" class="submit">Join a group</a> <a href="{% url group_add gname %}" class="submit">Create {{gname}}</a>
{% endfor %}
<a href="{% url group_search %}" class="submit">Join group</a>
</p> </p>
{% endif %} {% endif %}
{% if object_list %} {% if object_list %}
...@@ -36,18 +38,21 @@ ...@@ -36,18 +38,21 @@
</thead> </thead>
<tbody> <tbody>
{% for o in object_list %} {% for o in object_list %}
{% with o.owner.all as owners %}
{% with o.members as members %}
{% with o.approved_members as approved_members %}
<tr> <tr>
<td><a class="extra-link" href="{% url group_detail o.id %}">{{o.name}}</a></td> <td><a class="extra-link" href="{% url group_detail o.id %}">{{o.name}}</a></td>
<td>{{o.kind}}</td> <td>{{o.kind}}</td>
<td>{{o.issue_date|date:"d/m/Y"}}</td> <td>{{o.issue_date|date:"d/m/Y"}}</td>
<td>{{o.expiration_date|date:"d/m/Y"}}</td> <td>{{o.expiration_date|date:"d/m/Y"}}</td>
<td>{% if user in o.owner.all %}Yes{% else %}No{% endif %}</td> <td>{% if user in owners %}Yes{% else %}No{% endif %}</td>
<td>{{ o.approved_members|length }}/{{ o.members|length }}</td> <td>{{ approved_members|length }}/{{ members|length }}</td>
<td>{% if o.is_enabled %}Yes{% else %}No{% endif %}</td> <td>{% if o.is_enabled %}Yes{% else %}No{% endif %}</td>
<td>{% if o.moderation_enabled%}Yes{% else %}No{% endif %}</td> <td>{% if o.moderation_enabled%}Yes{% else %}No{% endif %}</td>
{% if user in o.approved_members %} {% if user in approved_members %}
<td>Active</td> <td>Active</td>
{% if user not in o.owner.all %} {% if user not in owners %}
<td> <td>
<form action="{% url group_leave o.id %}" method="post"class="login innerlabels">{% csrf_token %} <form action="{% url group_leave o.id %}" method="post"class="login innerlabels">{% csrf_token %}
<div class="form-row submit clearfix"> <div class="form-row submit clearfix">
...@@ -57,7 +62,7 @@ ...@@ -57,7 +62,7 @@
</td> </td>
{% endif %} {% endif %}
{% else %} {% else %}
{% if user in o.members %} {% if user in members %}
<td>Pending</td> <td>Pending</td>
{% else %} {% else %}
<td>Not member</td> <td>Not member</td>
...@@ -76,6 +81,9 @@ ...@@ -76,6 +81,9 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</tr> </tr>
{% endwith %}
{% endwith %}
{% endwith %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
......
...@@ -50,11 +50,9 @@ urlpatterns = patterns('astakos.im.views', ...@@ -50,11 +50,9 @@ urlpatterns = patterns('astakos.im.views',
url(r'^approval_terms/(?P<term_id>\d+)/?$', 'approval_terms'), url(r'^approval_terms/(?P<term_id>\d+)/?$', 'approval_terms'),
url(r'^password/?$', 'change_password', {}, name='password_change'), url(r'^password/?$', 'change_password', {}, name='password_change'),
url(r'^resources/?$', 'resource_list', {}, name='resource_list'), url(r'^resources/?$', 'resource_list', {}, name='resource_list'),
url(r'^group/add/?$', 'group_add', {}, name='group_add'), url(r'^group/add/(?P<kind_name>\w+)?$', 'group_add', {}, name='group_add'),
url(r'^group/list/?$', 'group_list', {}, name='group_list'), url(r'^group/list/?$', 'group_list', {}, name='group_list'),
url(r'^group/(?P<group_id>\d+)/?$', 'group_detail', {}, name='group_detail'), url(r'^group/(?P<group_id>\d+)/?$', 'group_detail', {}, name='group_detail'),
#url(r'^group/(?P<group_id>\d+)/policies/list/?$', 'group_policies_list', {}, name='group_policies_list'),
url(r'^group/(?P<group_id>\d+)/policies/add/?$', 'group_policies_add', {}, name='group_policies_add'),
url(r'^group/search/?$', 'group_search', {}, name='group_search'), url(r'^group/search/?$', 'group_search', {}, name='group_search'),
url(r'^group/(?P<group_id>\d+)/join/?$', 'group_join', {}, name='group_join'), url(r'^group/(?P<group_id>\d+)/join/?$', 'group_join', {}, name='group_join'),
url(r'^group/(?P<group_id>\d+)/leave/?$', 'group_leave', {}, name='group_leave'), url(r'^group/(?P<group_id>\d+)/leave/?$', 'group_leave', {}, name='group_leave'),
......
...@@ -57,7 +57,7 @@ from django.utils.translation import ugettext as _ ...@@ -57,7 +57,7 @@ from django.utils.translation import ugettext as _
from django.views.generic.create_update import * from django.views.generic.create_update import *
from django.views.generic.list_detail import * from django.views.generic.list_detail import *
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms, AstakosGroup from astakos.im.models import AstakosUser, Invitation, ApprovalTerms, AstakosGroup, Resource
from astakos.im.activation_backends import get_backend, SimpleBackend from astakos.im.activation_backends import get_backend, SimpleBackend
from astakos.im.util import get_context, prepare_response, set_cookie, get_query from astakos.im.util import get_context, prepare_response, set_cookie, get_query
from astakos.im.forms import * from astakos.im.forms import *
...@@ -589,15 +589,69 @@ def change_email(request, activation_key=None, ...@@ -589,15 +589,69 @@ def change_email(request, activation_key=None,
@signed_terms_required @signed_terms_required
@login_required @login_required
def group_add(request): def group_add(request, kind_name='default'):
return create_object(request, try:
form_class=get_astakos_group_creation_form(request), kind = GroupKind.objects.get(name = kind_name)
post_save_redirect = '/im/group/%(id)s/') except:
return HttpResponseBadRequest(_('No such group kind'))
template_name=None,
template_loader=loader
extra_context=None
post_save_redirect='/im/group/%(id)s/'
login_required=False
context_processors=None
model, form_class = get_model_and_form_class(
model=None,
form_class=AstakosGroupCreationForm
)
# TODO better approach???
resources = dict( (str(r.id), r) for r in Resource.objects.select_related().all() )
if request.method == 'POST':
form = form_class(request.POST, request.FILES, resources=resources)
if form.is_valid():
new_object = form.save()
new_object.owners = [request.user]
for (rid, limit) in form.resources():
try:
r = resources[rid]
except KeyError, e:
logger.exception(e)
# Should I stay or should I go???
continue
else:
new_object.astakosgroupquota_set.create(
resource = r,
limit = limit
)
msg = _("The %(verbose_name)s was created successfully.") %\
{"verbose_name": model._meta.verbose_name}
messages.success(request, msg, fail_silently=True)
return redirect(post_save_redirect, new_object)
else:
now = datetime.now()
data = {
'kind':kind,
'issue_date':now,
'expiration_date':now + timedelta(days=30)
}
form = form_class(data, resources=resources)
# Create the template, context, response
template_name = "%s/%s_form.html" % (
model._meta.app_label,
model._meta.object_name.lower()
)
t = template_loader.get_template(template_name)
c = RequestContext(request, {
'form': form
}, context_processors)
return HttpResponse(t.render(c))
@signed_terms_required @signed_terms_required
@login_required @login_required
def group_list(request): def group_list(request):
list = AstakosGroup.objects.filter(membership__person=request.user) list = request.user.astakos_groups.select_related().all()
return object_list(request, queryset=list) return object_list(request, queryset=list)
@signed_terms_required @signed_terms_required
...@@ -608,34 +662,13 @@ def group_detail(request, group_id): ...@@ -608,34 +662,13 @@ def group_detail(request, group_id):
except AstakosGroup.DoesNotExist: except AstakosGroup.DoesNotExist:
return HttpResponseBadRequest(_('Invalid group.')) return HttpResponseBadRequest(_('Invalid group.'))
return object_detail(request, return object_detail(request,
AstakosGroup.objects.all(), AstakosGroup.objects.all(),
object_id=group_id, object_id=group_id,
extra_context = {'form':get_astakos_group_policy_creation_form(group), extra_context = {'quota':group.quota}
'quota':group.quota, )
'more_policies':group.has_undefined_policies})
@signed_terms_required
@login_required
def group_policies_list(request, group_id):
list = AstakosGroupQuota.objects.filter(group__id=group_id)
return object_list(request, queryset=list)
@signed_terms_required @signed_terms_required
@login_required @login_required
def group_policies_add(request, group_id):
try:
group = AstakosGroup.objects.select_related().get(id=group_id)
except AstakosGroup.DoesNotExist:
return HttpResponseBadRequest(_('Invalid group.'))
return create_object(request,
form_class=get_astakos_group_policy_creation_form(group),
template_name = 'im/astakosgroup_detail.html',
post_save_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)),
extra_context = {'group':group,
'quota':group.quota,
'more_policies':group.has_undefined_policies})
@signed_terms_required
@login_required
def group_approval_request(request, group_id): def group_approval_request(request, group_id):
return HttpResponse() return HttpResponse()
...@@ -653,57 +686,87 @@ def group_search(request, extra_context={}, **kwargs): ...@@ -653,57 +686,87 @@ def group_search(request, extra_context={}, **kwargs):
queryset = AstakosGroup.objects.select_related().filter(name=q) queryset = AstakosGroup.objects.select_related().filter(name=q)
f = MembershipCreationForm f = MembershipCreationForm
for g in queryset: for g in queryset:
join_forms[g.name] = f(dict(group=g, join_forms[g.name] = f(
person=request.user, dict(
date_requested=datetime.now().strftime("%d/%m/%Y"))) group=g,
return object_list(request, person=request.user,
queryset, date_requested=datetime.now().strftime("%d/%m/%Y")
template_name='im/astakosgroup_list.html', )
extra_context=dict(form=form, is_search=True, join_forms=join_forms)) )
return render_response(template='im/astakosgroup_list.html', return object_list(
form = form, request,
context_instance=get_context(request)) queryset,
template_name='im/astakosgroup_list.html',
extra_context=dict(
form=form,
is_search=True,
join_forms=join_forms
)
)
return render_response(
template='im/astakosgroup_list.html',
form = form,
context_instance=get_context(request)
)
@signed_terms_required @signed_terms_required
@login_required @login_required
def group_join(request, group_id): def group_join(request, group_id):
return create_object(request, return create_object(
model=Membership, request,
template_name='im/astakosgroup_list.html', model=Membership,
post_save_redirect = reverse('group_detail', kwargs=dict(group_id=group_id))) template_name='im/astakosgroup_list.html',
post_save_redirect = reverse(
'group_detail',
kwargs=dict(group_id=group_id)
)
)
@signed_terms_required @signed_terms_required
@login_required @login_required
def group_leave(request, group_id): def group_leave(request, group_id):
try: try:
m = Membership.objects.select_related().get(group__id=group_id, person=request.user) m = Membership.objects.select_related().get(
group__id=group_id,
person=request.user
)
except Membership.DoesNotExist: