savannah-cvs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Savannah-cvs] [SCM] Savane-cleanup framework branch, master, updated. e


From: Sylvain Beucler
Subject: [Savannah-cvs] [SCM] Savane-cleanup framework branch, master, updated. e88f33cdd0fddf4819cda2ce99edac964ab54240
Date: Sun, 25 Jul 2010 11:40:48 +0000

This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Savane-cleanup framework".

The branch, master has been updated
       via  e88f33cdd0fddf4819cda2ce99edac964ab54240 (commit)
       via  cd2d37afddda1f726da1484a2d1abcc8622ad95b (commit)
      from  abdb1145a454ad1ae41c9f6391b9bec1778d4a1e (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://git.savannah.gnu.org/cgit/savane-cleanup/framework.git/commit/?id=e88f33cdd0fddf4819cda2ce99edac964ab54240

commit e88f33cdd0fddf4819cda2ce99edac964ab54240
Author: Sylvain Beucler <address@hidden>
Date:   Sun Jul 25 13:40:28 2010 +0200

    Encapsulte user<->group functions in Membership

diff --git a/savane/perms.py b/savane/perms.py
index 262d8e1..094d5fa 100644
--- a/savane/perms.py
+++ b/savane/perms.py
@@ -21,14 +21,6 @@ from django.shortcuts import get_object_or_404
 from savane.middleware.exception import HttpAppException
 import savane.svmain.models as svmain_models
 
-def is_member(user, group):
-    return group.user_set.filter(pk=user.pk).count() > 0
-
-def is_admin(user, group):
-    return (is_member(user, group)
-            and svmain_models.Membership.objects
-            .filter(user=user, group=group, admin_flags='A'))
-
 def only_project_admin(f, error_msg="Permission Denied"):
     """
     Decorator to keep non-members out of project administration
@@ -37,7 +29,7 @@ def only_project_admin(f, error_msg="Permission Denied"):
     """
     def _f(request, *args, **kwargs):
         group = get_object_or_404(auth_models.Group, name=kwargs['slug'])
-        if request.user.is_anonymous() or not is_admin(request.user, group):
+        if request.user.is_anonymous() or not 
svmain_models.Membership.is_admin(request.user, group):
             raise HttpAppException(error_msg)
         return f(request, *args, **kwargs)
     return _f
diff --git a/savane/svmain/models.py b/savane/svmain/models.py
index 58a2942..ec221b3 100644
--- a/savane/svmain/models.py
+++ b/savane/svmain/models.py
@@ -549,6 +549,29 @@ class Membership(models.Model):
     # Deprecated
     #forum_flags int(11) default NULL
 
+    def save(self, force_insert=False, force_update=False):
+        """
+        Update the matching User<->Group relationship
+        """
+        if self.admin_flags != 'P':
+            self.group.user_set.add(self.user)
+        if self.admin_flags == 'P':
+            self.group.user_set.remove(self.user)
+        super(self.__class__, self).save(force_insert, force_update)
+    def delete(self, using=None):
+        self.group.user_set.remove(self.user)
+        super(self.__class__, self).delete(using)
+
+    @staticmethod
+    def is_member(user, group):
+        return group.user_set.filter(pk=user.pk).count() > 0
+
+    @staticmethod
+    def is_admin(user, group):
+        return (Membership.is_member(user, group)
+                and Membership.objects
+                .filter(user=user, group=group, admin_flags='A').count() > 0)
+
     @staticmethod
     def query_active_memberships_raw(conn, fields):
         """
diff --git a/savane/svmain/views.py b/savane/svmain/views.py
index 9ed740a..fd15be7 100644
--- a/savane/svmain/views.py
+++ b/savane/svmain/views.py
@@ -74,6 +74,8 @@ def group_admin_members(request, slug, extra_context={}):
 
     if request.method == 'POST':
         for membership in memberships:
+            # Note: Membership's save() and delete() update the
+            # matching User<->Group relationship.
             if request.user != membership.user: # don't unadmin or remove 
myself
                 # admin / unadmin 
                 if request.POST.get('admin_%d' % membership.pk, None):
@@ -88,13 +90,11 @@ def group_admin_members(request, slug, extra_context={}):
                         messages.success(request, "permissions of %s updated." 
% membership.user)
                 # remove members
                 if request.POST.get('remove_%d' % membership.pk, None):
-                    group.user_set.remove(membership.user)
                     membership.delete()
                     messages.success(request, "User %s deleted from the 
project." % membership.user)
         # approve pending membership
         for membership in pending_memberships:
             if request.POST.get('approve_%d' % membership.pk, None):
-                group.user_set.add(membership.user)
                 membership.admin_flags = ''
                 membership.save()
                 messages.success(request, "User %s added to the project." % 
membership.user)

http://git.savannah.gnu.org/cgit/savane-cleanup/framework.git/commit/?id=cd2d37afddda1f726da1484a2d1abcc8622ad95b

commit cd2d37afddda1f726da1484a2d1abcc8622ad95b
Author: Sylvain Beucler <address@hidden>
Date:   Sun Jul 25 12:47:51 2010 +0200

    Interface to manage project members (TODO: search for members)

diff --git a/savane/django_utils.py b/savane/django_utils.py
new file mode 100644
index 0000000..97f3fcb
--- /dev/null
+++ b/savane/django_utils.py
@@ -0,0 +1,47 @@
+# Batch-decorator for urlpatterns
+# http://www.djangosnippets.org/snippets/532/
+# Author: miracle2k (Jan 1, 2008)
+# 
+# I hate legal-speak as much as anybody, but on a site which is geared
+# toward sharing code there has to be at least a little bit of it, so
+# here goes:
+# 
+# By creating an account here you agree to three things:
+# 
+#   1. That you will only post code which you wrote yourself and that
+#   you have the legal right to release under these terms.
+# 
+#   2. That you grant any third party who sees the code you post a
+#   royalty-free, non-exclusive license to copy and distribute that
+#   code and to make and distribute derivative works based on that
+#   code. You may include license terms in snippets you post, if you
+#   wish to use a particular license (such as the BSD license or GNU
+#   GPL), but that license must permit royalty-free copying,
+#   distribution and modification of the code to which it is applied.
+# 
+#   3. That if you post code of which you are not the author or for
+#   which you do not have the legal right to distribute according to
+#   these terms, you will indemnify and hold harmless the operators of
+#   this site and any third parties who are exposed to liability as a
+#   result of your actions.
+#
+# If you can't legally agree to these terms, or don't want to, you
+# cannot create an account here.
+
+from django.core.urlresolvers import RegexURLPattern
+from django.conf.urls.defaults import patterns
+class DecoratedURLPattern(RegexURLPattern):
+    def resolve(self, *args, **kwargs):
+        result = RegexURLPattern.resolve(self, *args, **kwargs)
+        if result:
+            result = list(result)
+            result[0] = self._decorate_with(result[0])
+        return result
+def decorated_patterns(prefix, func, *args):
+    result = patterns(prefix, *args)
+    if func:
+        for p in result:
+            if isinstance(p, RegexURLPattern):
+                p.__class__ = DecoratedURLPattern
+                p._decorate_with = func
+    return result
diff --git a/savane/middleware/exception.py b/savane/middleware/exception.py
new file mode 100644
index 0000000..0b6fbf5
--- /dev/null
+++ b/savane/middleware/exception.py
@@ -0,0 +1,34 @@
+# Catch app-specific exception and display it to the user
+# Copyright (C) 2009, 2010  Sylvain Beucler
+#
+# This file is part of Savane.
+# 
+# Savane is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# Savane is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+class HttpAppException(Exception):
+    pass
+
+class HttpCatchAppExceptionMiddleware(object):
+    def process_exception(self, request, exception):
+        """
+        Only catch our HttpAppException (and derivate classes)
+        """
+        if isinstance(exception, HttpAppException):
+            return render_to_response('error.html',
+                                      { 'error' : exception.message },
+                                      context_instance=RequestContext(request))
+        return None
diff --git a/savane/my/urls.py b/savane/my/urls.py
index 1e34866..8acbdc5 100644
--- a/savane/my/urls.py
+++ b/savane/my/urls.py
@@ -25,27 +25,11 @@ import views
 import savane.svmain.models as svmain_models
 import django.contrib.auth.models as auth_models
 from savane.my.filters import *
+from savane.django_utils import decorated_patterns
 
-# Batch-decorator for urlpatterns
-# http://www.djangosnippets.org/snippets/532/
-from django.core.urlresolvers import RegexURLPattern
-class DecoratedURLPattern(RegexURLPattern):
-    def resolve(self, *args, **kwargs):
-        result = RegexURLPattern.resolve(self, *args, **kwargs)
-        if result:
-            result = list(result)
-            result[0] = self._decorate_with(result[0])
-        return result
-def decorated_patterns(prefix, func, *args):
-    result = patterns(prefix, *args)
-    if func:
-        for p in result:
-            if isinstance(p, RegexURLPattern):
-                p.__class__ = DecoratedURLPattern
-                p._decorate_with = func
-    return result
+urlpatterns = patterns ('',)
 
-urlpatterns = decorated_patterns ('', login_required,
+urlpatterns += decorated_patterns ('', login_required,
   url(r'^$', direct_to_template,
       { 'template' : 'my/index.html',
         'extra_context' : { 'title' : 'My account', }, },
diff --git a/savane/perms.py b/savane/perms.py
new file mode 100644
index 0000000..262d8e1
--- /dev/null
+++ b/savane/perms.py
@@ -0,0 +1,43 @@
+# Permission restrictions to use as view decorators
+# Copyright (C) 2010  Sylvain Beucler
+#
+# This file is part of Savane.
+# 
+# Savane is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# Savane is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import django.contrib.auth.models as auth_models
+from django.shortcuts import get_object_or_404
+from savane.middleware.exception import HttpAppException
+import savane.svmain.models as svmain_models
+
+def is_member(user, group):
+    return group.user_set.filter(pk=user.pk).count() > 0
+
+def is_admin(user, group):
+    return (is_member(user, group)
+            and svmain_models.Membership.objects
+            .filter(user=user, group=group, admin_flags='A'))
+
+def only_project_admin(f, error_msg="Permission Denied"):
+    """
+    Decorator to keep non-members out of project administration
+    screens.  Identifies the current group using the 'slug' keyword
+    parameter.
+    """
+    def _f(request, *args, **kwargs):
+        group = get_object_or_404(auth_models.Group, name=kwargs['slug'])
+        if request.user.is_anonymous() or not is_admin(request.user, group):
+            raise HttpAppException(error_msg)
+        return f(request, *args, **kwargs)
+    return _f
diff --git a/savane/svmain/models.py b/savane/svmain/models.py
index cf41348..58a2942 100644
--- a/savane/svmain/models.py
+++ b/savane/svmain/models.py
@@ -533,6 +533,7 @@ class Membership(models.Model):
       blank=True, help_text="membership properties")
     onduty = models.BooleanField(default=True,
       help_text="Untick to hide emeritous members from the project page")
+    since = models.DateField(blank=True, null=True)
 
     # TODO: split news params
     #news_flags int(11) default NULL
diff --git a/savane/svmain/urls.py b/savane/svmain/urls.py
index 0d62b8a..8e1245e 100644
--- a/savane/svmain/urls.py
+++ b/savane/svmain/urls.py
@@ -19,11 +19,14 @@
 
 from django.conf.urls.defaults import *
 from django.views.generic.list_detail import object_list, object_detail
+from django.contrib.auth.decorators import login_required
 
 import savane.svmain.models as svmain_models
 import django.contrib.auth.models as auth_models
 import views
 from savane.filters import search
+from savane.perms import only_project_admin
+from savane.django_utils import decorated_patterns
 
 urlpatterns = patterns ('',)
 
@@ -82,13 +85,17 @@ urlpatterns += patterns ('',
   url(r'^pr/(?P<slug>[-\w]+)/$', views.group_redir),
   url(r'^projects/(?P<slug>[-\w]+)/$', views.group_redir),
   url(r'^p/(?P<slug>[-\w]+)/join/$', views.group_join),
+)
+urlpatterns += decorated_patterns ('', only_project_admin,
   url(r'^p/(?P<slug>[-\w]+)/admin/$', views.group_admin,
       { 'extra_context' : { 'title' : 'Administration Summary' }, },
       name='savane.svmain.group_admin'),
   url(r'^p/(?P<slug>[-\w]+)/admin/members/$', views.group_admin_members,
       { 'extra_context' : { 'title' : 'Administration Summary: Manage Members' 
}, },
       name='savane.svmain.group_admin_members'),
+)
 
+urlpatterns += patterns ('',
   url(r'^license/$', 'django.views.generic.list_detail.object_list',
       { 'queryset' : svmain_models.License.objects.all(),
         'extra_context' : { 'title' : 'License list' }, },
diff --git a/savane/svmain/views.py b/savane/svmain/views.py
index aa5233c..9ed740a 100644
--- a/savane/svmain/views.py
+++ b/savane/svmain/views.py
@@ -55,11 +55,56 @@ def group_admin(request, slug, extra_context={}):
 @render_to('svmain/group_admin_members.html', mimetype=None)
 def group_admin_members(request, slug, extra_context={}):
     group = get_object_or_404(auth_models.Group, name=slug)
-    members = group.user_set.all()
+
+    # If using a non-Savane groups base, prepare membership metadata
+    user_pks = 
svmain_models.Membership.objects.filter(group=group).values_list('user__pk', 
flat=True)
+    missing_members = group.user_set.exclude(pk__in=user_pks)
+    for member in missing_members:
+        svmain_models.Membership(user=member, group=group, 
admin_flags='A').save()
+
+    # If a membership does not have a matching User<->Group relationship, 
remove it
+    user_pks = group.user_set.values_list('pk', flat=True)
+    invalid_memberships = 
svmain_models.Membership.objects.exclude(user__in=user_pks).exclude(admin_flags='P')
+    invalid_memberships.delete()
+
+
+    memberships = 
svmain_models.Membership.objects.filter(group=group).exclude(admin_flags='P')
+    pending_memberships = svmain_models.Membership.objects.filter(group=group, 
admin_flags='P')
+
+
+    if request.method == 'POST':
+        for membership in memberships:
+            if request.user != membership.user: # don't unadmin or remove 
myself
+                # admin / unadmin 
+                if request.POST.get('admin_%d' % membership.pk, None):
+                    if membership.admin_flags != 'A':
+                        membership.admin_flags = 'A'
+                        membership.save()
+                        messages.success(request, "permissions of %s updated." 
% membership.user)
+                else:
+                    if membership.admin_flags != '':
+                        membership.admin_flags = ''
+                        membership.save()
+                        messages.success(request, "permissions of %s updated." 
% membership.user)
+                # remove members
+                if request.POST.get('remove_%d' % membership.pk, None):
+                    group.user_set.remove(membership.user)
+                    membership.delete()
+                    messages.success(request, "User %s deleted from the 
project." % membership.user)
+        # approve pending membership
+        for membership in pending_memberships:
+            if request.POST.get('approve_%d' % membership.pk, None):
+                group.user_set.add(membership.user)
+                membership.admin_flags = ''
+                membership.save()
+                messages.success(request, "User %s added to the project." % 
membership.user)
+        return HttpResponseRedirect('')  # reload
+
 
     context = {
         'group' : group,
-        'members' : members,
+        'memberships' : memberships,
+        'pending_memberships' : pending_memberships,
         }
     context.update(extra_context)
     return context
diff --git a/settings_default.py b/settings_default.py
index b965d2f..fbfafa7 100644
--- a/settings_default.py
+++ b/settings_default.py
@@ -82,6 +82,7 @@ MIDDLEWARE_CLASSES = (
     'django.contrib.messages.middleware.MessageMiddleware',
 
     'savane.middleware.debug.DebugFooter',
+    'savane.middleware.exception.HttpCatchAppExceptionMiddleware',
 )
 
 ROOT_URLCONF = 'urls'
diff --git a/static_media/savane/css/Savannah.css 
b/static_media/savane/css/Savannah.css
index cfacb57..2cddfe6 100644
--- a/static_media/savane/css/Savannah.css
+++ b/static_media/savane/css/Savannah.css
@@ -2,7 +2,7 @@
  *
  * Copyright (C) 2002-2006  Mathieu Roy
  * Copyright (C) 2005  Stéphane Urbanovski
- * Copyright (C) 2005  Sylvain Beucler
+ * Copyright (C) 2005, 2010  Sylvain Beucler
  *
  * This file is part of Savane.
  *
diff --git a/templates/error.html b/templates/error.html
index a0730c9..f77689d 100644
--- a/templates/error.html
+++ b/templates/error.html
@@ -1,5 +1,19 @@
 {% extends "base.html" %}
 
+{% block title %}
+Error
+{% endblock %}
+
+{% block top %}
+{% endblock %}
+
 {% block content %}
-<p style='color: red'>{{error}}</p>
+<div class="main"><a name="top"></a>
+<div id="feedback" class="feedbackerror">
+  <span class="feedbackerrortitle">
+    <img src="{{STATIC_MEDIA_URL}}savane/images/common/bool1/wrong.orig.png" 
class="feedbackimage" alt="" />
+    Error:
+  </span><br/>
+  {{error}}
+</div>
 {% endblock content %}
diff --git a/templates/svmain/group_admin_members.html 
b/templates/svmain/group_admin_members.html
index 66980fe..c02ce8a 100644
--- a/templates/svmain/group_admin_members.html
+++ b/templates/svmain/group_admin_members.html
@@ -14,6 +14,49 @@
 
 {% block content %}
 
+<p>Members:</p>
+<form action="" method="POST">{% csrf_token %}
+<table>
+<tr><th>User</th><th>Admin</th><th>Remove?</th></tr>
+{% for membership in memberships %}
+<tr>
+  <td>{{ membership.user.get_full_name }} &lt;{{ membership.user.username 
}}&gt;</td>
+  <td>
+    {% ifequal request.user membership.user %}
+    <em>{% trans "You are Admin" %}</em>
+    {% else %}
+    <input type="checkbox" name="admin_{{membership.pk}}" {% ifequal 
membership.admin_flags "A" %}checked="checked"{% endifequal %}/>
+    {% endifequal %}
+  </td>
+  <td>
+    {% ifequal request.user membership.user %}
+    -
+    {% else %}    <input type="checkbox" name="remove_{{membership.pk}}" />
+    {% endifequal %}
+  </td>
+</tr>
+{% endfor %}
+</table>
+
+{% if pending_memberships %}
+  <p>Requests for inclusion:</p>
+  <table>
+  <tr><th>User</th><th>Approve</th></tr>
+  {% for membership in pending_memberships %}
+  <tr>
+    <td>{{ membership.user.get_full_name }} &lt;{{ membership.user.username 
}}&gt;</td>
+    <td><input type="checkbox" name="approve_{{membership.pk}}" /></td>
+  </tr>
+  {% endfor %}
+  </table>
+{% else %}
+  <p><em>{% trans "No requests for inclusion" %}</em></p>
+{% endif %}
+
+<p><input type="submit" value="OK" /></p>
+</form>
+
+<p><a href="add/">{% trans "Add users to group" %}</a></p>
 
 {% endblock %}
 

-----------------------------------------------------------------------

Summary of changes:
 savane/django_utils.py                    |   47 +++++++++++++++++++++++++++
 savane/middleware/exception.py            |   34 ++++++++++++++++++++
 savane/my/urls.py                         |   22 ++-----------
 savane/perms.py                           |   35 ++++++++++++++++++++
 savane/svmain/models.py                   |   24 ++++++++++++++
 savane/svmain/urls.py                     |    7 ++++
 savane/svmain/views.py                    |   49 +++++++++++++++++++++++++++-
 settings_default.py                       |    1 +
 static_media/savane/css/Savannah.css      |    2 +-
 templates/error.html                      |   16 +++++++++-
 templates/svmain/group_admin_members.html |   43 +++++++++++++++++++++++++
 11 files changed, 257 insertions(+), 23 deletions(-)
 create mode 100644 savane/django_utils.py
 create mode 100644 savane/middleware/exception.py
 create mode 100644 savane/perms.py


hooks/post-receive
-- 
Savane-cleanup framework



reply via email to

[Prev in Thread] Current Thread [Next in Thread]