Revision 0f4fa26d

b/snf-astakos-app/astakos/im/context_processors.py
36 36
        GLOBAL_MESSAGES, PROFILE_EXTRA_LINKS
37 37
from astakos.im.api import get_menu
38 38
from astakos.im.util import get_query
39
from astakos.im.models import GroupKind
39 40

  
40 41
from django.conf import settings
41 42
from django.core.urlresolvers import reverse
......
79 80
        for item in menu_items:
80 81
            item['is_active'] = absolute(request.path) == item['url']
81 82
        return {'menu':menu_items}
83

  
84
def group_kinds(request):
85
    return {'group_kinds': GroupKind.objects.exclude(name='default').values_list('name', flat=True)}
b/snf-astakos-app/astakos/im/forms.py
490 490
            user.save()
491 491
        return user
492 492

  
493
def get_astakos_group_creation_form(request):
494
    class AstakosGroupCreationForm(forms.ModelForm):
495
        issue_date = forms.DateField(widget=SelectDateWidget(), initial=datetime.now())
496
        # TODO set initial in exact one month
497
        expiration_date = forms.DateField(widget=SelectDateWidget(), initial = datetime.now() + timedelta(days=30))
498
        kind = forms.ModelChoiceField(queryset=GroupKind.objects.all(), empty_label=None)
499
        name = forms.URLField()
500
        
501
        class Meta:
502
            model = AstakosGroup
503
        
504
        def __init__(self, *args, **kwargs):
505
            super(AstakosGroupCreationForm, self).__init__(*args, **kwargs)
506
            self.fields.keyOrder = ['kind', 'name', 'desc', 'issue_date',
507
                                    'expiration_date', 'estimated_participants',
508
                                    'moderation_enabled']
509
        
510
        def save(self, commit=True):
511
            g = super(AstakosGroupCreationForm, self).save(commit=False)
512
            if commit: 
513
                g.save()
514
                g.owner = [request.user]
515
                g.approve_member(request.user)
516
            return g
493
class AstakosGroupCreationForm(forms.ModelForm):
494
#     issue_date = forms.DateField(widget=SelectDateWidget())
495
#     expiration_date = forms.DateField(widget=SelectDateWidget())
496
    kind = forms.ModelChoiceField(
497
        queryset=GroupKind.objects.all(),
498
        label="",
499
        widget=forms.HiddenInput()
500
    )
501
    name = forms.URLField()
517 502
    
518
    return AstakosGroupCreationForm
519

  
520
def get_astakos_group_policy_creation_form(astakosgroup):
521
    class AstakosGroupPolicyCreationForm(forms.ModelForm):
522
        choices = Resource.objects.filter(~Q(astakosgroup=astakosgroup))
523
        resource = forms.ModelChoiceField(queryset=choices, empty_label=None)
524
        # TODO check that it does not hit the db
525
        group = forms.ModelChoiceField(queryset=AstakosGroup.objects.all(), initial=astakosgroup, widget=forms.HiddenInput())
526
        
527
        class Meta:
528
            model = AstakosGroupQuota
503
    class Meta:
504
        model = AstakosGroup
529 505
    
530
    return AstakosGroupPolicyCreationForm
506
    def __init__(self, *args, **kwargs):
507
        try:
508
            resources = kwargs.pop('resources')
509
        except KeyError:
510
            resources = {}
511
        super(AstakosGroupCreationForm, self).__init__(*args, **kwargs)
512
        self.fields.keyOrder = ['kind', 'name', 'desc', 'issue_date',
513
                                'expiration_date', 'estimated_participants',
514
                                'moderation_enabled']
515
        for id, r in resources.iteritems():
516
            self.fields['resource_%s' % id] = forms.IntegerField(
517
                label=r,
518
                required=False,
519
                help_text=_('Leave it blank for no additional quota.')
520
            )
521
        
522
    def resources(self):
523
        for name, value in self.cleaned_data.items():
524
            prefix, delimiter, suffix = name.partition('resource_')
525
            if suffix:
526
                # yield only those having a value
527
                if not value:
528
                    continue
529
                yield (suffix, value)
531 530

  
532 531
class AstakosGroupSearchForm(forms.Form):
533 532
    q = forms.CharField(max_length=200, label='')
534 533

  
535 534
class MembershipCreationForm(forms.ModelForm):
536 535
    # TODO check not to hit the db
537
    group = forms.ModelChoiceField(queryset=AstakosGroup.objects.all(), widget=forms.HiddenInput())
538
    person = forms.ModelChoiceField(queryset=AstakosUser.objects.all(), widget=forms.HiddenInput())
539
    date_requested = forms.DateField(widget=forms.HiddenInput(), input_formats="%d/%m/%Y")
536
    group = forms.ModelChoiceField(
537
        queryset=AstakosGroup.objects.all(),
538
        widget=forms.HiddenInput()
539
    )
540
    person = forms.ModelChoiceField(
541
        queryset=AstakosUser.objects.all(),
542
        widget=forms.HiddenInput()
543
    )
544
    date_requested = forms.DateField(
545
        widget=forms.HiddenInput(),
546
        input_formats="%d/%m/%Y"
547
    )
540 548
    
541 549
    class Meta:
542 550
        model = Membership
b/snf-astakos-app/astakos/im/management/commands/group_list.py
65 65
        if options.get('pending'):
66 66
            groups = filter(lambda g: g.is_disabled, groups)
67 67
        
68
        labels = ('id', 'name', 'enabled', 'permissions')
69
        columns = (3, 12, 12, 50)
68
        labels = ('id', 'name', 'enabled', 'moderation', 'permissions')
69
        columns = (3, 25, 12, 12, 50)
70 70
        
71 71
        if not options.get('csv'):
72 72
            line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
......
78 78
            fields = (  str(group.id),
79 79
                        group.name,
80 80
                        format_bool(group.is_enabled),
81
                        format_bool(group.moderation_enabled),
81 82
                        ','.join(p.codename for p in group.permissions.all()))
82 83
            
83 84
            if options.get('csv'):
b/snf-astakos-app/astakos/im/models.py
154 154
        self.save()
155 155
    
156 156
    def approve_member(self, person):
157
        try:
158
            self.membership_set.create(person=person, date_joined=datetime.now())
159
        except IntegrityError:
160
            m = self.membership_set.get(person=person)
161
            m.date_joined = datetime.now()
162
            m.save()
157
        m, created = self.membership_set.get_or_create(person=person)
158
        # update date_joined in any case
159
        m.date_joined=datetime.now()
160
        m.save()
163 161
    
164 162
    def disapprove_member(self, person):
165 163
        self.membership_set.remove(person=person)
......
175 173
    
176 174
    @property
177 175
    def quota(self):
178
        d = {}
179
        for q in  self.astakosgroupquota_set.all():
180
            d[q.resource.name] = q.limit
176
        d = defaultdict(int)
177
        for q in self.astakosgroupquota_set.all():
178
            d[q.resource] += q.limit
181 179
        return d
182 180
    
183 181
    @property
184 182
    def has_undefined_policies(self):
185 183
        # TODO: can avoid query?
186 184
        return Resource.objects.filter(~Q(astakosgroup=self)).exists()
185
    
186
    @property
187
    def owners(self):
188
        return self.owner.all()
189
    
190
    @owners.setter
191
    def owners(self, l):
192
        self.owner = l
193
        map(self.approve_member, l)
187 194

  
188 195
class AstakosUser(User):
189 196
    """
......
567 574
    if instance.is_superuser:
568 575
        create_astakos_user(instance)
569 576

  
570
post_save.connect(superuser_post_save, sender=User)
577
post_save.connect(superuser_post_save, sender=User)
578

  
579
def get_resources():
580
    # use cache
581
    return Resource.objects.select_related().all()
b/snf-astakos-app/astakos/im/synnefo_settings.py
57 57
    'astakos.im.context_processors.invitations',
58 58
    'astakos.im.context_processors.menu',
59 59
    'astakos.im.context_processors.custom_messages',
60
    'astakos.im.context_processors.group_kinds',
60 61
    'synnefo.lib.context_processors.cloudbar'
61 62
]
62 63

  
b/snf-astakos-app/astakos/im/templates/im/astakosgroup_detail.html
95 95
            <p>No policies</p>
96 96
        {% endif %}
97 97
    </div>
98
    {% if user in object.owner.all and more_policies %}
99
    <div class="rightcol">
100
        <form action="{% url group_policies_add object.id %}" method="post" class="innerlabels signup">{% csrf_token %}
101
            <h2><span>NEW POLICY</span></h2>
102
                {% include "im/form_render.html" %}
103
                <div class="form-row submit">
104
                    <input type="submit" class="submit altcol" value="+" />
105
                </div>
106
        </form>
107
    </div>
108
    {% endif %}
109 98
</div>
110 99
{% endblock %}
b/snf-astakos-app/astakos/im/templates/im/astakosgroup_form.html
7 7
          <h2><span>NEW GROUP</span></h2>
8 8
            {% include "im/form_render.html" %}
9 9
            <div class="form-row submit">
10
                <input type="submit" class="submit altcol" value="ADD POLICIES" />
10
                <input type="submit" class="submit altcol" value="SUBMIT" />
11 11
            </div>
12 12
    </form>
13 13
</div>
b/snf-astakos-app/astakos/im/templates/im/astakosgroup_list.html
14 14
    </form>
15 15
    {% else %}
16 16
        <p class="submit-rt">
17
            <a href="{% url group_add %}" class="submit">Create a group</a>
18
            <a href="{% url group_search %}" class="submit">Join a group</a>
17
            {% for gname in group_kinds %}
18
            <a href="{% url group_add gname %}" class="submit">Create {{gname}}</a>
19
            {% endfor %}
20
            <a href="{% url group_search %}" class="submit">Join group</a>
19 21
        </p>
20 22
    {% endif %}
21 23
      {% if object_list %}
......
36 38
            </thead>
37 39
            <tbody>
38 40
              {% for o in object_list %}
41
                {% with o.owner.all as owners %}
42
                    {% with o.members as members %}
43
                        {% with o.approved_members as approved_members %}
39 44
              <tr>
40 45
                <td><a class="extra-link" href="{% url group_detail o.id %}">{{o.name}}</a></td>
41 46
                <td>{{o.kind}}</td>
42 47
                <td>{{o.issue_date|date:"d/m/Y"}}</td>
43 48
                <td>{{o.expiration_date|date:"d/m/Y"}}</td>
44
                <td>{% if user in o.owner.all %}Yes{% else %}No{% endif %}</td>
45
                <td>{{ o.approved_members|length }}/{{ o.members|length }}</td>
49
                <td>{% if user in owners %}Yes{% else %}No{% endif %}</td>
50
                <td>{{ approved_members|length }}/{{ members|length }}</td>
46 51
                <td>{% if o.is_enabled %}Yes{% else %}No{% endif %}</td>
47 52
                <td>{% if o.moderation_enabled%}Yes{% else %}No{% endif %}</td>
48
                {% if user in o.approved_members %}
53
                {% if user in approved_members %}
49 54
                    <td>Active</td>
50
                    {% if user not in o.owner.all %}
55
                    {% if user not in owners %}
51 56
                    <td>
52 57
                        <form action="{% url group_leave o.id %}" method="post"class="login innerlabels">{% csrf_token %}
53 58
                            <div class="form-row submit clearfix">
......
57 62
                    </td>
58 63
                    {% endif %}
59 64
                {% else %}
60
                    {% if user in o.members %}
65
                    {% if user in members %}
61 66
                        <td>Pending</td>
62 67
                    {% else %}
63 68
                        <td>Not member</td>
......
76 81
                    {% endif %}
77 82
                {% endif %}
78 83
              </tr>
84
                        {% endwith %}
85
                    {% endwith %}
86
                {% endwith %}
79 87
              {% endfor %}
80 88
            </tbody>
81 89
        </table>
b/snf-astakos-app/astakos/im/urls.py
50 50
    url(r'^approval_terms/(?P<term_id>\d+)/?$', 'approval_terms'),
51 51
    url(r'^password/?$', 'change_password', {}, name='password_change'),
52 52
    url(r'^resources/?$', 'resource_list', {}, name='resource_list'),
53
    url(r'^group/add/?$', 'group_add', {}, name='group_add'),
53
    url(r'^group/add/(?P<kind_name>\w+)?$', 'group_add', {}, name='group_add'),
54 54
    url(r'^group/list/?$', 'group_list', {}, name='group_list'),
55 55
    url(r'^group/(?P<group_id>\d+)/?$', 'group_detail', {}, name='group_detail'),
56
    #url(r'^group/(?P<group_id>\d+)/policies/list/?$', 'group_policies_list', {}, name='group_policies_list'),
57
    url(r'^group/(?P<group_id>\d+)/policies/add/?$', 'group_policies_add', {}, name='group_policies_add'),
58 56
    url(r'^group/search/?$', 'group_search', {}, name='group_search'),
59 57
    url(r'^group/(?P<group_id>\d+)/join/?$', 'group_join', {}, name='group_join'),
60 58
    url(r'^group/(?P<group_id>\d+)/leave/?$', 'group_leave', {}, name='group_leave'),
b/snf-astakos-app/astakos/im/views.py
57 57
from django.views.generic.create_update import *
58 58
from django.views.generic.list_detail import *
59 59

  
60
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms, AstakosGroup
60
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms, AstakosGroup, Resource
61 61
from astakos.im.activation_backends import get_backend, SimpleBackend
62 62
from astakos.im.util import get_context, prepare_response, set_cookie, get_query
63 63
from astakos.im.forms import *
......
589 589

  
590 590
@signed_terms_required
591 591
@login_required
592
def group_add(request):
593
    return create_object(request,
594
                         form_class=get_astakos_group_creation_form(request),
595
                         post_save_redirect = '/im/group/%(id)s/')
592
def group_add(request, kind_name='default'):
593
    try:
594
        kind = GroupKind.objects.get(name = kind_name)
595
    except:
596
        return HttpResponseBadRequest(_('No such group kind'))
597
    
598
    template_name=None,
599
    template_loader=loader
600
    extra_context=None
601
    post_save_redirect='/im/group/%(id)s/'
602
    login_required=False
603
    context_processors=None
604
    model, form_class = get_model_and_form_class(
605
        model=None,
606
        form_class=AstakosGroupCreationForm
607
    )
608
    # TODO better approach???
609
    resources = dict( (str(r.id), r) for r in Resource.objects.select_related().all() )
610
    if request.method == 'POST':
611
        form = form_class(request.POST, request.FILES, resources=resources)
612
        if form.is_valid():
613
            new_object = form.save()
614
            new_object.owners = [request.user]
615
            for (rid, limit) in form.resources():
616
                try:
617
                    r = resources[rid]
618
                except KeyError, e:
619
                    logger.exception(e)
620
                    # Should I stay or should I go???
621
                    continue
622
                else:
623
                    new_object.astakosgroupquota_set.create(
624
                        resource = r,
625
                        limit = limit
626
                    )
627
            msg = _("The %(verbose_name)s was created successfully.") %\
628
                                    {"verbose_name": model._meta.verbose_name}
629
            messages.success(request, msg, fail_silently=True)
630
            return redirect(post_save_redirect, new_object)
631
    else:
632
        now = datetime.now()
633
        data = {
634
            'kind':kind,
635
            'issue_date':now,
636
            'expiration_date':now + timedelta(days=30)
637
        }
638
        form = form_class(data, resources=resources)
639

  
640
    # Create the template, context, response
641
    template_name = "%s/%s_form.html" % (
642
        model._meta.app_label,
643
        model._meta.object_name.lower()
644
    )
645
    t = template_loader.get_template(template_name)
646
    c = RequestContext(request, {
647
        'form': form
648
    }, context_processors)
649
    return HttpResponse(t.render(c))
596 650

  
597 651
@signed_terms_required
598 652
@login_required
599 653
def group_list(request):
600
    list = AstakosGroup.objects.filter(membership__person=request.user)
654
    list = request.user.astakos_groups.select_related().all()
601 655
    return object_list(request, queryset=list)
602 656

  
603 657
@signed_terms_required
......
608 662
    except AstakosGroup.DoesNotExist:
609 663
        return HttpResponseBadRequest(_('Invalid group.'))
610 664
    return object_detail(request,
611
                         AstakosGroup.objects.all(),
612
                         object_id=group_id,
613
                         extra_context = {'form':get_astakos_group_policy_creation_form(group),
614
                                          'quota':group.quota,
615
                                          'more_policies':group.has_undefined_policies})
616

  
617
@signed_terms_required
618
@login_required
619
def group_policies_list(request, group_id):
620
    list = AstakosGroupQuota.objects.filter(group__id=group_id)
621
    return object_list(request, queryset=list)
665
         AstakosGroup.objects.all(),
666
         object_id=group_id,
667
         extra_context = {'quota':group.quota}
668
    )
622 669

  
623 670
@signed_terms_required
624 671
@login_required
625
def group_policies_add(request, group_id):
626
    try:
627
        group = AstakosGroup.objects.select_related().get(id=group_id)
628
    except AstakosGroup.DoesNotExist:
629
        return HttpResponseBadRequest(_('Invalid group.'))
630
    return create_object(request,
631
                         form_class=get_astakos_group_policy_creation_form(group),
632
                         template_name = 'im/astakosgroup_detail.html',
633
                         post_save_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)),
634
                         extra_context = {'group':group,
635
                                          'quota':group.quota,
636
                                          'more_policies':group.has_undefined_policies})
637
@signed_terms_required
638
@login_required
639 672
def group_approval_request(request, group_id):
640 673
    return HttpResponse()
641 674

  
......
653 686
            queryset = AstakosGroup.objects.select_related().filter(name=q)
654 687
            f = MembershipCreationForm
655 688
            for g in queryset:
656
                join_forms[g.name] = f(dict(group=g,
657
                                            person=request.user,
658
                                            date_requested=datetime.now().strftime("%d/%m/%Y")))
659
            return object_list(request,
660
                                queryset,
661
                                template_name='im/astakosgroup_list.html',
662
                                extra_context=dict(form=form, is_search=True, join_forms=join_forms))
663
    return render_response(template='im/astakosgroup_list.html',
664
                            form = form,
665
                            context_instance=get_context(request))
689
                join_forms[g.name] = f(
690
                    dict(
691
                        group=g,
692
                        person=request.user,
693
                        date_requested=datetime.now().strftime("%d/%m/%Y")
694
                    )
695
                )
696
            return object_list(
697
                request,
698
                queryset,
699
                template_name='im/astakosgroup_list.html',
700
                extra_context=dict(
701
                    form=form,
702
                    is_search=True,
703
                    join_forms=join_forms
704
                )
705
            )
706
    return render_response(
707
        template='im/astakosgroup_list.html',
708
        form = form,
709
        context_instance=get_context(request)
710
    )
666 711

  
667 712
@signed_terms_required
668 713
@login_required
669 714
def group_join(request, group_id):
670
    return create_object(request,
671
                         model=Membership,
672
                         template_name='im/astakosgroup_list.html',
673
                         post_save_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)))
715
    return create_object(
716
        request,
717
        model=Membership,
718
        template_name='im/astakosgroup_list.html',
719
        post_save_redirect = reverse(
720
            'group_detail',
721
            kwargs=dict(group_id=group_id)
722
        )
723
    )
674 724

  
675 725
@signed_terms_required
676 726
@login_required
677 727
def group_leave(request, group_id):
678 728
    try:
679
        m = Membership.objects.select_related().get(group__id=group_id, person=request.user)
729
        m = Membership.objects.select_related().get(
730
            group__id=group_id,
731
            person=request.user
732
        )
680 733
    except Membership.DoesNotExist:
681 734
        return HttpResponseBadRequest(_('Invalid membership.'))
682 735
    if request.user in m.group.owner.all():
683 736
        return HttpResponseForbidden(_('Owner can not leave the group.'))
684
    return delete_object(request,
685
                         model=Membership,
686
                         object_id = m.id,
687
                         template_name='im/astakosgroup_list.html',
688
                         post_delete_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)))
737
    return delete_object(
738
        request,
739
        model=Membership,
740
        object_id = m.id,
741
        template_name='im/astakosgroup_list.html',
742
        post_delete_redirect = reverse(
743
            'group_detail',
744
            kwargs=dict(group_id=group_id)
745
        )
746
    )
689 747

  
690 748
def handle_membership():
691 749
    def decorator(func):
692 750
        @wraps(func)
693 751
        def wrapper(request, group_id, user_id):
694 752
            try:
695
                m = Membership.objects.select_related().get(group__id=group_id, person__id=user_id)
753
                m = Membership.objects.select_related().get(
754
                    group__id=group_id,
755
                    person__id=user_id
756
                )
696 757
            except Membership.DoesNotExist:
697 758
                return HttpResponseBadRequest(_('Invalid membership.'))
698 759
            else:
699 760
                if request.user not in m.group.owner.all():
700 761
                    return HttpResponseForbidden(_('User is not a group owner.'))
701 762
                func(request, m)
702
                return render_response(template='im/astakosgroup_detail.html',
703
                                       context_instance=get_context(request),
704
                                       object=m.group,
705
                                       quota=m.group.quota,
706
                                       more_policies=m.group.has_undefined_policies)
763
                return render_response(
764
                    template='im/astakosgroup_detail.html',
765
                    context_instance=get_context(request),
766
                    object=m.group,
767
                    quota=m.group.quota,
768
                    more_policies=m.group.has_undefined_policies
769
                )
707 770
        return wrapper
708 771
    return decorator
709 772

  
......
738 801
@signed_terms_required
739 802
@login_required
740 803
def resource_list(request):
741
    return render_response(template='im/astakosuserquota_list.html',
742
                           context_instance=get_context(request),
743
                           quota=request.user.quota)
804
    return render_response(
805
        template='im/astakosuserquota_list.html',
806
        context_instance=get_context(request),
807
        quota=request.user.quota
808
    )

Also available in: Unified diff