Progress VII
authorSofia Papagiannaki <papagian@gmail.com>
Mon, 27 Aug 2012 16:53:35 +0000 (19:53 +0300)
committerSofia Papagiannaki <papagian@gmail.com>
Mon, 27 Aug 2012 16:53:35 +0000 (19:53 +0300)
* add policies during group creation
* improve performance (reduce db access)

snf-astakos-app/astakos/im/context_processors.py
snf-astakos-app/astakos/im/forms.py
snf-astakos-app/astakos/im/management/commands/group_list.py
snf-astakos-app/astakos/im/models.py
snf-astakos-app/astakos/im/synnefo_settings.py
snf-astakos-app/astakos/im/templates/im/astakosgroup_detail.html
snf-astakos-app/astakos/im/templates/im/astakosgroup_form.html
snf-astakos-app/astakos/im/templates/im/astakosgroup_list.html
snf-astakos-app/astakos/im/urls.py
snf-astakos-app/astakos/im/views.py

index 479830f..a017b2a 100644 (file)
@@ -36,6 +36,7 @@ from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, IM_STATIC_URL,
         GLOBAL_MESSAGES, PROFILE_EXTRA_LINKS
 from astakos.im.api import get_menu
 from astakos.im.util import get_query
+from astakos.im.models import GroupKind
 
 from django.conf import settings
 from django.core.urlresolvers import reverse
@@ -79,3 +80,6 @@ def menu(request):
         for item in menu_items:
             item['is_active'] = absolute(request.path) == item['url']
         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
index 1b40a1b..0698457 100644 (file)
@@ -490,53 +490,61 @@ class ExtendedPasswordChangeForm(PasswordChangeForm):
             user.save()
         return user
 
-def get_astakos_group_creation_form(request):
-    class AstakosGroupCreationForm(forms.ModelForm):
-        issue_date = forms.DateField(widget=SelectDateWidget(), initial=datetime.now())
-        # TODO set initial in exact one month
-        expiration_date = forms.DateField(widget=SelectDateWidget(), initial = datetime.now() + timedelta(days=30))
-        kind = forms.ModelChoiceField(queryset=GroupKind.objects.all(), empty_label=None)
-        name = forms.URLField()
-        
-        class Meta:
-            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
+class AstakosGroupCreationForm(forms.ModelForm):
+#     issue_date = forms.DateField(widget=SelectDateWidget())
+#     expiration_date = forms.DateField(widget=SelectDateWidget())
+    kind = forms.ModelChoiceField(
+        queryset=GroupKind.objects.all(),
+        label="",
+        widget=forms.HiddenInput()
+    )
+    name = forms.URLField()
     
-    return AstakosGroupCreationForm
-
-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
+    class Meta:
+        model = AstakosGroup
     
-    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):
     q = forms.CharField(max_length=200, label='')
 
 class MembershipCreationForm(forms.ModelForm):
     # TODO check not to hit the db
-    group = forms.ModelChoiceField(queryset=AstakosGroup.objects.all(), 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")
+    group = forms.ModelChoiceField(
+        queryset=AstakosGroup.objects.all(),
+        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:
         model = Membership
index 85df802..856e618 100644 (file)
@@ -65,8 +65,8 @@ class Command(BaseCommand):
         if options.get('pending'):
             groups = filter(lambda g: g.is_disabled, groups)
         
-        labels = ('id', 'name', 'enabled', 'permissions')
-        columns = (3, 12, 12, 50)
+        labels = ('id', 'name', 'enabled', 'moderation', 'permissions')
+        columns = (3, 25, 12, 12, 50)
         
         if not options.get('csv'):
             line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
@@ -78,6 +78,7 @@ class Command(BaseCommand):
             fields = (  str(group.id),
                         group.name,
                         format_bool(group.is_enabled),
+                        format_bool(group.moderation_enabled),
                         ','.join(p.codename for p in group.permissions.all()))
             
             if options.get('csv'):
index ac0fce6..165c1a0 100644 (file)
@@ -154,12 +154,10 @@ class AstakosGroup(Group):
         self.save()
     
     def approve_member(self, person):
-        try:
-            self.membership_set.create(person=person, date_joined=datetime.now())
-        except IntegrityError:
-            m = self.membership_set.get(person=person)
-            m.date_joined = datetime.now()
-            m.save()
+        m, created = self.membership_set.get_or_create(person=person)
+        # update date_joined in any case
+        m.date_joined=datetime.now()
+        m.save()
     
     def disapprove_member(self, person):
         self.membership_set.remove(person=person)
@@ -175,15 +173,24 @@ class AstakosGroup(Group):
     
     @property
     def quota(self):
-        d = {}
-        for q in  self.astakosgroupquota_set.all():
-            d[q.resource.name] = q.limit
+        d = defaultdict(int)
+        for q in self.astakosgroupquota_set.all():
+            d[q.resource] += q.limit
         return d
     
     @property
     def has_undefined_policies(self):
         # TODO: can avoid query?
         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):
     """
@@ -567,4 +574,8 @@ def superuser_post_save(sender, instance, **kwargs):
     if instance.is_superuser:
         create_astakos_user(instance)
 
-post_save.connect(superuser_post_save, sender=User)
\ No newline at end of file
+post_save.connect(superuser_post_save, sender=User)
+
+def get_resources():
+    # use cache
+    return Resource.objects.select_related().all()
\ No newline at end of file
index 4a23941..c7f7f4a 100644 (file)
@@ -57,6 +57,7 @@ context_processors = [
     'astakos.im.context_processors.invitations',
     'astakos.im.context_processors.menu',
     'astakos.im.context_processors.custom_messages',
+    'astakos.im.context_processors.group_kinds',
     'synnefo.lib.context_processors.cloudbar'
 ]
 
index 2b14197..774d270 100644 (file)
             <p>No policies</p>
         {% endif %}
     </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>
 {% endblock %}
index a0c6c04..f1b454a 100644 (file)
@@ -7,7 +7,7 @@
           <h2><span>NEW GROUP</span></h2>
             {% include "im/form_render.html" %}
             <div class="form-row submit">
-                <input type="submit" class="submit altcol" value="ADD POLICIES" />
+                <input type="submit" class="submit altcol" value="SUBMIT" />
             </div>
     </form>
 </div>
index 255356e..819ed49 100644 (file)
     </form>
     {% else %}
         <p class="submit-rt">
-            <a href="{% url group_add %}" class="submit">Create a group</a>
-            <a href="{% url group_search %}" class="submit">Join a group</a>
+            {% for gname in group_kinds %}
+            <a href="{% url group_add gname %}" class="submit">Create {{gname}}</a>
+            {% endfor %}
+            <a href="{% url group_search %}" class="submit">Join group</a>
         </p>
     {% endif %}
       {% if object_list %}
             </thead>
             <tbody>
               {% for o in object_list %}
+                {% with o.owner.all as owners %}
+                    {% with o.members as members %}
+                        {% with o.approved_members as approved_members %}
               <tr>
                 <td><a class="extra-link" href="{% url group_detail o.id %}">{{o.name}}</a></td>
                 <td>{{o.kind}}</td>
                 <td>{{o.issue_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>{{ o.approved_members|length }}/{{ o.members|length }}</td>
+                <td>{% if user in owners %}Yes{% else %}No{% endif %}</td>
+                <td>{{ approved_members|length }}/{{ members|length }}</td>
                 <td>{% if o.is_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>
-                    {% if user not in o.owner.all %}
+                    {% if user not in owners %}
                     <td>
                         <form action="{% url group_leave o.id %}" method="post"class="login innerlabels">{% csrf_token %}
                             <div class="form-row submit clearfix">
@@ -57,7 +62,7 @@
                     </td>
                     {% endif %}
                 {% else %}
-                    {% if user in o.members %}
+                    {% if user in members %}
                         <td>Pending</td>
                     {% else %}
                         <td>Not member</td>
@@ -76,6 +81,9 @@
                     {% endif %}
                 {% endif %}
               </tr>
+                        {% endwith %}
+                    {% endwith %}
+                {% endwith %}
               {% endfor %}
             </tbody>
         </table>
index fcc3c17..1ee9a9b 100644 (file)
@@ -50,11 +50,9 @@ urlpatterns = patterns('astakos.im.views',
     url(r'^approval_terms/(?P<term_id>\d+)/?$', 'approval_terms'),
     url(r'^password/?$', 'change_password', {}, name='password_change'),
     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/(?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/(?P<group_id>\d+)/join/?$', 'group_join', {}, name='group_join'),
     url(r'^group/(?P<group_id>\d+)/leave/?$', 'group_leave', {}, name='group_leave'),
index 21edf32..ecc7d4a 100644 (file)
@@ -57,7 +57,7 @@ from django.utils.translation import ugettext as _
 from django.views.generic.create_update 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.util import get_context, prepare_response, set_cookie, get_query
 from astakos.im.forms import *
@@ -589,15 +589,69 @@ def change_email(request, activation_key=None,
 
 @signed_terms_required
 @login_required
-def group_add(request):
-    return create_object(request,
-                         form_class=get_astakos_group_creation_form(request),
-                         post_save_redirect = '/im/group/%(id)s/')
+def group_add(request, kind_name='default'):
+    try:
+        kind = GroupKind.objects.get(name = kind_name)
+    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
 @login_required
 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)
 
 @signed_terms_required
@@ -608,34 +662,13 @@ def group_detail(request, group_id):
     except AstakosGroup.DoesNotExist:
         return HttpResponseBadRequest(_('Invalid group.'))
     return object_detail(request,
-                         AstakosGroup.objects.all(),
-                         object_id=group_id,
-                         extra_context = {'form':get_astakos_group_policy_creation_form(group),
-                                          '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)
+         AstakosGroup.objects.all(),
+         object_id=group_id,
+         extra_context = {'quota':group.quota}
+    )
 
 @signed_terms_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):
     return HttpResponse()
 
@@ -653,57 +686,87 @@ def group_search(request, extra_context={}, **kwargs):
             queryset = AstakosGroup.objects.select_related().filter(name=q)
             f = MembershipCreationForm
             for g in queryset:
-                join_forms[g.name] = f(dict(group=g,
-                                            person=request.user,
-                                            date_requested=datetime.now().strftime("%d/%m/%Y")))
-            return object_list(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))
+                join_forms[g.name] = f(
+                    dict(
+                        group=g,
+                        person=request.user,
+                        date_requested=datetime.now().strftime("%d/%m/%Y")
+                    )
+                )
+            return object_list(
+                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
 @login_required
 def group_join(request, group_id):
-    return create_object(request,
-                         model=Membership,
-                         template_name='im/astakosgroup_list.html',
-                         post_save_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)))
+    return create_object(
+        request,
+        model=Membership,
+        template_name='im/astakosgroup_list.html',
+        post_save_redirect = reverse(
+            'group_detail',
+            kwargs=dict(group_id=group_id)
+        )
+    )
 
 @signed_terms_required
 @login_required
 def group_leave(request, group_id):
     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:
         return HttpResponseBadRequest(_('Invalid membership.'))
     if request.user in m.group.owner.all():
         return HttpResponseForbidden(_('Owner can not leave the group.'))
-    return delete_object(request,
-                         model=Membership,
-                         object_id = m.id,
-                         template_name='im/astakosgroup_list.html',
-                         post_delete_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)))
+    return delete_object(
+        request,
+        model=Membership,
+        object_id = m.id,
+        template_name='im/astakosgroup_list.html',
+        post_delete_redirect = reverse(
+            'group_detail',
+            kwargs=dict(group_id=group_id)
+        )
+    )
 
 def handle_membership():
     def decorator(func):
         @wraps(func)
         def wrapper(request, group_id, user_id):
             try:
-                m = Membership.objects.select_related().get(group__id=group_id, person__id=user_id)
+                m = Membership.objects.select_related().get(
+                    group__id=group_id,
+                    person__id=user_id
+                )
             except Membership.DoesNotExist:
                 return HttpResponseBadRequest(_('Invalid membership.'))
             else:
                 if request.user not in m.group.owner.all():
                     return HttpResponseForbidden(_('User is not a group owner.'))
                 func(request, m)
-                return render_response(template='im/astakosgroup_detail.html',
-                                       context_instance=get_context(request),
-                                       object=m.group,
-                                       quota=m.group.quota,
-                                       more_policies=m.group.has_undefined_policies)
+                return render_response(
+                    template='im/astakosgroup_detail.html',
+                    context_instance=get_context(request),
+                    object=m.group,
+                    quota=m.group.quota,
+                    more_policies=m.group.has_undefined_policies
+                )
         return wrapper
     return decorator
 
@@ -738,6 +801,8 @@ def disapprove_member(request, membership):
 @signed_terms_required
 @login_required
 def resource_list(request):
-    return render_response(template='im/astakosuserquota_list.html',
-                           context_instance=get_context(request),
-                           quota=request.user.quota)
\ No newline at end of file
+    return render_response(
+        template='im/astakosuserquota_list.html',
+        context_instance=get_context(request),
+        quota=request.user.quota
+    )
\ No newline at end of file