From f555e85e3dd08cc2d869f387275debbac79b3149 Mon Sep 17 00:00:00 2001 From: Andrew Williams Date: Thu, 21 Oct 2010 16:27:47 +0100 Subject: [PATCH] First step of the groups subapp, view groups and request access done --- groups/__init__.py | 0 groups/app_defines.py | 22 +++++ groups/forms.py | 11 +++ groups/migrations/0001_initial.py | 116 +++++++++++++++++++++++++++ groups/migrations/__init__.py | 0 groups/models.py | 38 +++++++++ groups/tests.py | 23 ++++++ groups/urls.py | 9 +++ groups/views.py | 64 +++++++++++++++ settings.py | 1 + templates/base.html | 17 ++-- templates/groups/create_request.html | 14 ++++ templates/groups/group_list.html | 23 ++++++ urls.py | 1 + 14 files changed, 332 insertions(+), 7 deletions(-) create mode 100644 groups/__init__.py create mode 100644 groups/app_defines.py create mode 100644 groups/forms.py create mode 100644 groups/migrations/0001_initial.py create mode 100644 groups/migrations/__init__.py create mode 100644 groups/models.py create mode 100644 groups/tests.py create mode 100644 groups/urls.py create mode 100644 groups/views.py create mode 100644 templates/groups/create_request.html create mode 100644 templates/groups/group_list.html diff --git a/groups/__init__.py b/groups/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/groups/app_defines.py b/groups/app_defines.py new file mode 100644 index 0000000..9a4b973 --- /dev/null +++ b/groups/app_defines.py @@ -0,0 +1,22 @@ +# Group Types +GROUP_TYPE_BUILTIN = 0 +GROUP_TYPE_PERMISSION = 1 +GROUP_TYPE_CORPORATE = 2 + +GROUP_TYPE_CHOICES = ( + (GROUP_TYPE_BUILTIN, 'Built-In'), + (GROUP_TYPE_PERMISSION, 'Permission'), + (GROUP_TYPE_CORPORATE, 'Corporate'), +) + + +# Request Status Codes +REQUEST_PENDING = 0 +REQUEST_ACCEPTED = 1 +REQUEST_REJECTED = 2 + +REQUEST_STATUS_CHOICES = ( + (REQUEST_PENDING, 'Pending'), + (REQUEST_ACCEPTED, 'Accepted'), + (REQUEST_REJECTED, 'Rejected'), +) diff --git a/groups/forms.py b/groups/forms.py new file mode 100644 index 0000000..0c9c5fd --- /dev/null +++ b/groups/forms.py @@ -0,0 +1,11 @@ +from django import forms +import settings + +from groups.models import GroupRequest +from groups.app_defines import * + +class GroupRequestForm(forms.ModelForm): + + class Meta: + model = GroupRequest + exclude = ('group', 'user', 'status', 'changed_by', 'changed_date', 'created_date') diff --git a/groups/migrations/0001_initial.py b/groups/migrations/0001_initial.py new file mode 100644 index 0000000..558c1e8 --- /dev/null +++ b/groups/migrations/0001_initial.py @@ -0,0 +1,116 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'GroupInformation' + db.create_table('groups_groupinformation', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('group', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.Group'], unique=True)), + ('type', self.gf('django.db.models.fields.IntegerField')()), + ('public', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)), + ('requestable', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)), + ('description', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('groups', ['GroupInformation']) + + # Adding M2M table for field admins on 'GroupInformation' + db.create_table('groups_groupinformation_admins', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('groupinformation', models.ForeignKey(orm['groups.groupinformation'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique('groups_groupinformation_admins', ['groupinformation_id', 'user_id']) + + # Adding model 'GroupRequest' + db.create_table('groups_grouprequest', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('group', self.gf('django.db.models.fields.related.ForeignKey')(related_name='requests', to=orm['auth.Group'])), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='grouprequests', to=orm['auth.User'])), + ('reason', self.gf('django.db.models.fields.TextField')()), + ('status', self.gf('django.db.models.fields.IntegerField')()), + ('changed_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('changed_date', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), + ('created_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + )) + db.send_create_signal('groups', ['GroupRequest']) + + + def backwards(self, orm): + + # Deleting model 'GroupInformation' + db.delete_table('groups_groupinformation') + + # Removing M2M table for field admins on 'GroupInformation' + db.delete_table('groups_groupinformation_admins') + + # Deleting model 'GroupRequest' + db.delete_table('groups_grouprequest') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'groups.groupinformation': { + 'Meta': {'object_name': 'GroupInformation'}, + 'admins': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'group': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'requestable': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'type': ('django.db.models.fields.IntegerField', [], {}) + }, + 'groups.grouprequest': { + 'Meta': {'object_name': 'GroupRequest'}, + 'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'created_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'requests'", 'to': "orm['auth.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reason': ('django.db.models.fields.TextField', [], {}), + 'status': ('django.db.models.fields.IntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'grouprequests'", 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['groups'] diff --git a/groups/migrations/__init__.py b/groups/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/groups/models.py b/groups/models.py new file mode 100644 index 0000000..4626d5b --- /dev/null +++ b/groups/models.py @@ -0,0 +1,38 @@ +from django.db import models +from django.db.models import signals +from django.contrib.auth.models import Group, User + +from groups.app_defines import * + +class GroupInformation(models.Model): + """ Extended group information """ + + group = models.OneToOneField(Group) + + type = models.IntegerField("Group Type", choices=GROUP_TYPE_CHOICES, default=GROUP_TYPE_BUILTIN) + admins = models.ManyToManyField(User) + public = models.BooleanField("Public", default=False) + requestable = models.BooleanField("Requestable", default=False) + + description = models.TextField() + + @staticmethod + def create_group(sender, instance, created, **kwargs): + if created: + profile, created = GroupInformation.objects.get_or_create(group=instance) + +signals.post_save.connect(GroupInformation.create_group, sender=Group) + + +class GroupRequest(models.Model): + """ Join requests for a group """ + + group = models.ForeignKey(Group, null=False, related_name='requests') + user = models.ForeignKey(User, null=False, related_name='grouprequests') + reason = models.TextField("Reason") + status = models.IntegerField("Request Status", choices=REQUEST_STATUS_CHOICES, null=False, default=REQUEST_PENDING) + + changed_by = models.ForeignKey(User) + changed_date = models.DateTimeField("Changed Date/Time", auto_now=True) + + created_date = models.DateTimeField("Created Date/Time", auto_now_add=True) diff --git a/groups/tests.py b/groups/tests.py new file mode 100644 index 0000000..2247054 --- /dev/null +++ b/groups/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/groups/urls.py b/groups/urls.py new file mode 100644 index 0000000..daffdd5 --- /dev/null +++ b/groups/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import * + +from groups import views + +urlpatterns = patterns('', + ('^$', views.index), + (r'^list/$', views.group_list), + (r'^request/(?P\d+)/$', views.create_request), +) diff --git a/groups/views.py b/groups/views.py new file mode 100644 index 0000000..23cd553 --- /dev/null +++ b/groups/views.py @@ -0,0 +1,64 @@ +from django.db.models import Q +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext +from django.core.urlresolvers import reverse + +from django.contrib.auth.models import Group +from django.contrib.auth.decorators import login_required + +from groups.app_defines import * +from groups.forms import GroupRequestForm + +def index(request): + return HttpResponseRedirect(reverse('groups.views.group_list')) + +@login_required +def group_list(request): + """ View all groups, for users only public ones """ + + if request.user.is_superuser: + groups = Group.objects.select_related('groupinformation').all() + else: + groups = Group.objects.select_related('groupinformation').filter(Q(groupinformation__public=True) | + Q(groupinformation__admins__in=[request.user]) | + Q(user_set__in=[request.user])) + + # Process the query into a list of tuples including status + group_list = [] + for group in groups: + if request.user in group.groupinformation.admins.all(): + group_list.append((group.id, group.name, 'Admin', group.groupinformation.requestable)) + elif request.user in group.user_set.all(): + group_list.append((group.id, group.name, 'Member', group.groupinformation.requestable)) + else: + group_list.append((group.id, group.name, None, group.groupinformation.requestable)) + + return render_to_response('groups/group_list.html', locals(), context_instance=RequestContext(request)) + +@login_required +def view_group(request): + pass + +@login_required +def create_request(request, groupid): + + group = get_object_or_404(Group, id=groupid) + + if not group.groupinformation.requestable and not request.user in group.user_set.all(): + return HttpResponseRedirect(reverse('groups.views.index')) + + if request.method == 'POST': + form = GroupRequestForm(request.POST) + if form.is_valid(): + obj = form.save(commit=False) + obj.user = request.user + obj.group = group + obj.changed_by = request.user + obj.save() + request.user.message_set.create(message="You membership request has been created.") + return HttpResponseRedirect(reverse('groups.views.index')) # Redirect after POST + else: + form = GroupRequestForm() # An unbound form + + return render_to_response('groups/create_request.html', locals(), context_instance=RequestContext(request)) diff --git a/settings.py b/settings.py index 20a7015..3d55fd1 100755 --- a/settings.py +++ b/settings.py @@ -85,6 +85,7 @@ INSTALLED_APPS = ( 'reddit', 'hr', 'sso', + 'groups', 'api', ) diff --git a/templates/base.html b/templates/base.html index d593dc4..42d3ede 100644 --- a/templates/base.html +++ b/templates/base.html @@ -16,20 +16,23 @@ diff --git a/templates/groups/create_request.html b/templates/groups/create_request.html new file mode 100644 index 0000000..042c369 --- /dev/null +++ b/templates/groups/create_request.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block title %}Create a Membership Request{% endblock %} + +{% block content %} +

Please fill in a reason why you like to be a member of this group and any supporting evidence as requested by the group adminship

+ +
+ +{{ form.as_table }} +
+ +
+{% endblock %} diff --git a/templates/groups/group_list.html b/templates/groups/group_list.html new file mode 100644 index 0000000..73a4a5c --- /dev/null +++ b/templates/groups/group_list.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block content %} + +

Groups List

+ +

This is the list of your current groups, and groups you can request to be a member of.

+ + +{% if group_list %} + +{% for id, group, status, requestable in group_list %} + + + + +{% endfor %} +
Group NameStatusActions
{{ group }}{{ status }}{% ifequal status None %}{% if requestable %}Request Membership{% endif %}{% endifequal %}
+{% else %} +No groups are available. +{% endif %} + +{% endblock %} diff --git a/urls.py b/urls.py index 7ed763e..2ee35c9 100644 --- a/urls.py +++ b/urls.py @@ -16,6 +16,7 @@ urlpatterns = patterns('', (r'^eveapi/', include('eve_proxy.urls')), (r'^api/', include('api.urls')), (r'^hr/', include('hr.urls')), + (r'^groups/', include('groups.urls')), ) urlpatterns += patterns('',