Commits

Anonymous committed d7d9e12

[all][l]: raw results of pinax-admin clone_project sample_group_project using pinax 0.7.

  • Participants

Comments (0)

Files changed (45)

+# -*- coding: utf-8 -*-
+
+__about__ = """
+This project demonstrates group functionality with a barebones group
+containing no extra content apps as well as two additional group types,
+tribes and projects, which show different membership approaches and
+content apps.
+"""

apps/about/__init__.py

Empty file added.

apps/about/models.py

+from django.db import models
+
+# Create your models here.

apps/about/urls.py

+from django.conf.urls.defaults import *
+from django.views.generic.simple import direct_to_template
+
+urlpatterns = patterns('',
+    url(r'^$', direct_to_template, {"template": "about/about.html"}, name="about"),
+    
+    url(r'^terms/$', direct_to_template, {"template": "about/terms.html"}, name="terms"),
+    url(r'^privacy/$', direct_to_template, {"template": "about/privacy.html"}, name="privacy"),
+    url(r'^dmca/$', direct_to_template, {"template": "about/dmca.html"}, name="dmca"),
+    
+    url(r'^what_next/$', direct_to_template, {"template": "about/what_next.html"}, name="what_next"),
+)

apps/about/views.py

+# Create your views here.

apps/basic_groups/__init__.py

Empty file added.

apps/basic_groups/admin.py

+from django.contrib import admin
+from basic_groups.models import BasicGroup
+
+class BasicGroupAdmin(admin.ModelAdmin):
+    list_display = ('name', 'slug', 'creator', 'created')
+
+admin.site.register(BasicGroup, BasicGroupAdmin)

apps/basic_groups/fixtures/basic_groups_auth.json

+[
+    {
+        "pk": 1,
+        "model": "auth.user",
+        "fields": {
+            "username": "tester",
+            "first_name": "",
+            "last_name": "",
+            "is_active": true,
+            "is_superuser": false,
+            "is_staff": false,
+            "last_login": "2009-05-11 12:00:00",
+            "groups": [],
+            "user_permissions": [],
+            "password": "sha1$1d0c5$628050793db288b947f240952a8468062440452d",
+            "email": "",
+            "date_joined": "2009-05-11 12:00:00"
+        }
+    }
+]

apps/basic_groups/forms.py

+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from basic_groups.models import BasicGroup
+
+# @@@ we should have auto slugs, even if suggested and overrideable
+
+class BasicGroupForm(forms.ModelForm):
+    
+    slug = forms.SlugField(max_length=20,
+        help_text = _("a short version of the name consisting only of letters, numbers, underscores and hyphens."),
+        error_message = _("This value must contain only letters, numbers, underscores and hyphens."))
+            
+    def clean_slug(self):
+        if BasicGroup.objects.filter(slug__iexact=self.cleaned_data["slug"]).count() > 0:
+            raise forms.ValidationError(_("A group already exists with that slug."))
+        return self.cleaned_data["slug"].lower()
+    
+    def clean_name(self):
+        if BasicGroup.objects.filter(name__iexact=self.cleaned_data["name"]).count() > 0:
+            raise forms.ValidationError(_("A group already exists with that name."))
+        return self.cleaned_data["name"]
+    
+    class Meta:
+        model = BasicGroup
+        fields = ('name', 'slug', 'description')
+
+
+# @@@ is this the right approach, to have two forms where creation and update fields differ?
+
+class BasicGroupUpdateForm(forms.ModelForm):
+    
+    def clean_name(self):
+        if BasicGroup.objects.filter(name__iexact=self.cleaned_data["name"]).count() > 0:
+            if self.cleaned_data["name"] == self.instance.name:
+                pass # same instance
+            else:
+                raise forms.ValidationError(_("A group already exists with that name."))
+        return self.cleaned_data["name"]
+    
+    class Meta:
+        model = BasicGroup
+        fields = ('name', 'description')

apps/basic_groups/management.py

+from django.conf import settings
+from django.db.models import signals
+from django.utils.translation import ugettext_noop as _
+
+if "notification" in settings.INSTALLED_APPS:
+    from notification import models as notification
+    
+    def create_notice_types(app, created_models, verbosity, **kwargs):
+        notification.create_notice_type("groups_new_member", _("New Group Member"), _("a group you are a member of has a new member"), default=1)
+        notification.create_notice_type("groups_created_new_member", _("New Member Of Group You Created"), _("a group you created has a new member"), default=2)
+        notification.create_notice_type("groups_new_group", _("New Group Created"), _("a new group has been created"), default=1)
+        
+    signals.post_syncdb.connect(create_notice_types, sender=notification)
+else:
+    print "Skipping creation of NoticeTypes as notification app not found"

apps/basic_groups/models.py

+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import  User
+from django.utils.translation import ugettext_lazy as _
+from django.db import models
+
+from groups.base import Group
+
+class BasicGroup(Group):
+    
+    members = models.ManyToManyField(User, related_name='basic_groups', verbose_name=_('members'))
+    
+    def get_absolute_url(self):
+        return reverse('group_detail', kwargs={'group_slug': self.slug})
+    
+    def get_url_kwargs(self):
+        return {'group_slug': self.slug}

apps/basic_groups/templates/basic_groups/base.html

+{% extends "site_base.html" %}
+
+{% load i18n %}
+
+{% block body_class %}groups{% endblock %}
+
+{% block extra_head %}
+<link rel="stylesheet" href="{{ STATIC_URL }}basic_groups/css/basic_groups.css" />
+{% endblock %}
+
+{% block subnav %}
+    <ul>
+        {% if user.is_authenticated %}
+            <li><a href="{% url group_create %}">{% trans "Create a Group" %}</a></li>
+            <li><a href="{% url your_groups %} ">{% trans "Your Groups" %}</a></li>
+        {% endif %}
+        <li><a href="{% url group_list %}">{% trans "All Groups" %}</a></li>
+    </ul>
+{% endblock %}

apps/basic_groups/templates/basic_groups/create.html

+{% extends "basic_groups/base.html" %}
+
+{% load i18n %}
+{% load uni_form %}
+{% load humanize %}
+{% load pagination_tags %}
+{% load order_by %}
+{% load extra_tagging_tags %}
+{% load group_tags %}
+{% load basic_group_tags %}
+
+{% block head_title %}{% blocktrans %}Create Group{% endblocktrans %}{% endblock %}
+
+{% block body %}
+    <h1>{% trans "Create Group" %}</h1>
+    
+    <p>{% trans "This is just a basic group example with no extra content items." %}</p>
+    {% if user.is_authenticated %}
+        <form id="group_form" method="POST" action="{% url group_create %}" class="uniForm">
+            <fieldset class="inlineLabels">
+                {{ group_form|as_uni_form }}
+                <div class="form_block">
+                    <input type="hidden" name="action" value="create" />
+                    <input type="submit" value="{% trans 'create' %}"/>
+                </div>
+            </fieldset>
+        </form>
+    {% else %}
+        {% url acct_signup as signup_url %}
+        {% url acct_login as login_url %}
+        <p>{% blocktrans %}<a href="{{ signup_url }}">Sign up</a> and <a href="{{ login_url }}">log in </a> to create your own group or join an existing one.{% endblocktrans %}</p>
+    {% endif %}
+    
+{% endblock %}

apps/basic_groups/templates/basic_groups/group.html

+{% extends "basic_groups/base.html" %}
+
+{% load i18n %}
+{# load avatar_tags #}
+{% load extra_tagging_tags %}
+{% load group_tags %}
+{% load basic_group_tags %}
+{% load theme_tags %}
+
+{% block head_title %}{{ group.name }}{% endblock %}
+
+{% block body %}
+
+    {% if user.is_authenticated %}
+        <div class="right_panel">
+            <div class="members">
+                <h2>{% trans "Members" %}</h2>
+                <table width="100%">
+                    {% for member in group.members.all %}
+                        {% if forloop.counter0|divisibleby:"3" %}<tr>{% endif %}
+                        <td>
+                            <div class="avatar">{# avatar member 40 #}</div>
+                            <div class="details"><a href="{% url profile_detail member.username %}" title="{{ member.username }}">{{ member.username }}</a></div>
+                        </td>
+                        {% if forloop.counter0|add:"1"|divisibleby:"3" %}</tr>{% endif %}
+                    {% endfor %}
+                    {% if group.members.all|length|divisibleby:"3" %}{% else %}</tr>{% endif %}
+                </table>
+            </div>
+        </div>
+    {% endif %}
+    
+    <div style="width: 550px;">
+        
+        <h1>{% trans "Group" %} {{ group.name }}</h1>
+        
+        <p>
+            {% trans "Slug:" %} <tt>{{ group.slug }}</tt><br />
+            {% trans "Creator:" %} <a href="{% url profile_detail group.creator.username %}">{{ group.creator }}</a><br />
+            {% trans "Created:" %} {{ group.created|date }}
+            <br />
+            {% show_tags_for group %}
+        </p>
+        
+        <p>{{ group.description }}</p>
+        
+        {% ifequal user group.creator %}
+            <div class="form-toggle">
+                <p><span id="edit-group-toggle">{% trans "Edit details" %}</span></p>
+                
+                <form id="edit-group" method="POST" action="">
+                    <table>
+                        {{ group_form }}
+                        <tr><td></td><td><input type="hidden" name="action" value="update" /><input type="submit" value="{% trans 'update' %}"/></td></tr>
+                    </table>
+                </form>
+            </div>
+            
+            {% ifequal group.members.all.count 1 %}
+                <p>{% silk "delete" %} <a href="#" onclick="$('#delete_group_form').toggle(); return false;">{% trans "Delete Group" %}</a><p>
+                 <form class="delete_form" id="delete_group_form" action="{% url group_delete group.slug %}" method="POST" style="display: none;">
+                     <input type="submit" value="{% trans "Delete Group" %}" /> (all content will be removed)
+                </form>
+            {% else %}
+                <p>{% trans "You are not the only member of the group so you cannot delete the group." %}</p>
+            {% endifequal %}
+            
+        {% endifequal %}
+        
+        {% if user.is_authenticated %}
+            <div>
+                <form method="POST" action="">
+                    {% if is_member %}
+                        {% ifequal user group.creator %}
+                            <p>{% trans "You are the creator of this group so can't leave (yet)." %}</p>
+                        {% else %}
+                            <input type="hidden" name="action" value="leave" />
+                            <input type="submit" value="{% trans "leave" %}"/>
+                        {% endifequal %}
+                    {% else %}
+                        <input type="hidden" name="action" value="join" />
+                        <input type="submit" value="{% trans "join group" %}"/>
+                    {% endif %}
+                </form>
+            </div>
+        {% else %}
+            {% url acct_signup as signup_url %}
+            {% url acct_login as login_url %}
+            <p>{% blocktrans %}<a href="{{ signup_url }}">Sign up</a> and <a href="{{ login_url }}">log in </a> to join this group.{% endblocktrans %}</p>
+        {% endif %}
+    </div>
+
+{% endblock %}
+
+{% block extra_body %}
+    <script type="text/javascript">
+        $(document).ready(function() {
+            $('#edit-group').hide();
+            $('#edit-group-toggle').click(function() {
+                $('#edit-group').slideToggle();
+                $('#edit-group').autoscroll();
+                return false;
+            });
+            if ($('#edit-group ul.errorlist').length) {
+                $('#edit-group').show();
+                $('#edit-group ul.errorlist').autoscroll();
+            }
+        });
+    </script>
+    
+{% endblock %}

apps/basic_groups/templates/basic_groups/group_item.html

+{% load i18n %}
+{% load extra_tagging_tags %}
+
+<dt>{{ group.name }} (<a href="{{ group.get_absolute_url }}">{{ group.slug }}</a>)</dt>
+<dd>{{ group.description }}
+    <div class="group_stats">
+        {% trans "Members:" %} <b>{{ group.members.count }}</b>
+        <br />
+        {% show_tags_for group %} {# @@@ #}
+    </div>
+</dd>

apps/basic_groups/templates/basic_groups/groups.html

+{% extends "basic_groups/base.html" %}
+
+{% load i18n %}
+{% load humanize %}
+{% load pagination_tags %}
+{% load order_by %}
+{% load extra_tagging_tags %}
+{% load group_tags %}
+{% load basic_group_tags %}
+
+{% block head_title %}{% blocktrans %}Groups{% endblocktrans %}{% endblock %}
+
+{% block body %}
+    <h1>{% trans "Groups" %}</h1>
+    
+    <p>{% trans "This is just a basic group example with no extra content items." %}</p>
+    {% if user.is_authenticated %}
+        <p><a href="{% url group_create %}">Create</a> your own group.</p>
+    {% else %}
+        {% url acct_signup as signup_url %}
+        {% url acct_login as login_url %}
+        <p>{% blocktrans %}<a href="{{ signup_url }}">Sign up</a> and <a href="{{ login_url }}">log in </a> to create your own group or join an existing one.{% endblocktrans %}</p>
+    {% endif %}
+    
+    {% autopaginate groups 10 %}
+    {% if groups %}
+        <dl>
+        {% for group in groups %}
+            {% show_group group %}
+        {% endfor %}
+        </dl>
+        {% paginate %}
+    {% else %}
+        <p>There are no groups yet.</p>
+    {% endif %}
+    
+{% endblock %}

apps/basic_groups/templates/basic_groups/your_groups.html

+{% extends "basic_groups/base.html" %}
+
+{% load i18n %}
+{% load pagination_tags %}
+{% load group_tags %}
+{% load basic_group_tags %}
+
+{% block head_title %}{% trans "Your Groups" %}{% endblock %}
+
+{% block body %}
+    
+    <h1>{% trans "Your Groups" %}</h1>
+    
+    {% if groups %}
+        <p>{% trans "These are the groups you participate in." %}</p>
+        
+        {% autopaginate groups %}
+        
+        <dl>
+            {% for group in groups %}
+                {% show_group group %}
+            {% endfor %}
+        </dl>
+        
+        {% paginate %}
+        
+    {% else %}
+        <p>You are not a member of any groups yet.</p>
+    {% endif %}
+    
+{% endblock %}

apps/basic_groups/templates/notification/groups_created_new_member/full.txt

+{% load i18n %}{% blocktrans with group.get_absolute_url as group_url %}{{ user }} has joined the group {{ group }}.
+
+http://{{ current_site }}{{ group_url }}
+{% endblocktrans %}

apps/basic_groups/templates/notification/groups_created_new_member/notice.html

+{% load i18n %}{% url profile_detail username=user.username as user_url %}
+{% blocktrans with group.get_absolute_url as group_url %}<a href="{{ user_url }}">{{ user }}</a> has joined the group <a href="{{ group_url }}">{{ group }}</a>.{% endblocktrans %}

apps/basic_groups/templates/notification/groups_new_member/full.txt

+{% load i18n %}{% blocktrans with group.get_absolute_url as group_url %}{{ user }} has joined the group {{ group }}.
+
+http://{{ current_site }}{{ group_url }}
+{% endblocktrans %}

apps/basic_groups/templates/notification/groups_new_member/notice.html

+{% load i18n %}{% url profile_detail username=user.username as user_url %}
+{% blocktrans with group.get_absolute_url as group_url %}<a href="{{ user_url }}">{{ user }}</a> has joined the group <a href="{{ group_url }}">{{ group }}</a>.{% endblocktrans %}

apps/basic_groups/templates/notification/groups_new_tribe/full.txt

+{% load i18n %}{% blocktrans with group.get_absolute_url as group_url %}A new group {{ group }} has been created.
+
+http://{{ current_site }}{{ group_url }}
+{% endblocktrans %}

apps/basic_groups/templates/notification/groups_new_tribe/notice.html

+{% load i18n %}
+{% blocktrans with group.get_absolute_url as group_url %}A new group <a href="{{ group_url }}">{{ group }}</a> has been created.{% endblocktrans %}

apps/basic_groups/templatetags/__init__.py

Empty file added.

apps/basic_groups/templatetags/basic_group_tags.py

+from django import template
+from basic_groups.forms import BasicGroupForm
+
+register = template.Library()
+
+
+@register.inclusion_tag("basic_groups/group_item.html")
+def show_group(group):
+    return {"group": group}
+
+
+# @@@ should move these next two as they aren't particularly group-specific
+
+@register.simple_tag
+def clear_search_url(request):
+    getvars = request.GET.copy()
+    if 'search' in getvars:
+        del getvars['search']
+    if len(getvars.keys()) > 0:
+        return "%s?%s" % (request.path, getvars.urlencode())
+    else:
+        return request.path
+
+
+@register.simple_tag
+def persist_getvars(request):
+    getvars = request.GET.copy()
+    if len(getvars.keys()) > 0:
+        return "?%s" % getvars.urlencode()
+    return ''

apps/basic_groups/tests/__init__.py

+from django.test import TestCase
+
+from basic_groups.models import BasicGroup
+
+class BasicGroupsTest(TestCase):
+    fixtures = ["basic_groups_auth.json"]
+    
+    def test_unauth_create_get(self):
+        """can an unauth'd user get to page?"""
+        
+        response = self.client.get("/groups/create/")
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response["location"], "http://testserver/account/login?next=/groups/create/")
+    
+    def test_auth_create_get(self):
+        """can an auth'd user get to page?"""
+        
+        logged_in = self.client.login(username="tester", password="tester")
+        self.assertTrue(logged_in)
+        response = self.client.get("/groups/create/")
+        self.assertEqual(response.status_code, 200)
+    
+    def test_unauth_create_post(self):
+        """can an unauth'd user post to create a new group?"""
+        
+        response = self.client.post("/groups/create/")
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response["location"], "http://testserver/account/login?next=/groups/create/")
+    
+    def test_auth_create_post(self):
+        """can an auth'd user post to create a new group?"""
+        
+        logged_in = self.client.login(username="tester", password="tester")
+        self.assertTrue(logged_in)
+        response = self.client.post("/groups/create/", {
+            "slug": "test",
+            "name": "Test Group",
+            "description": "A test group.",
+        })
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response["location"], "http://testserver/groups/group/test/")
+        self.assertEqual(BasicGroup.objects.get(slug="test").creator.username, "tester")
+        self.assertEqual(BasicGroup.objects.get(slug="test").members.all()[0].username, "tester")
+    
+    def test_auth_creator_membership(self):
+        """is membership for creator correct?"""
+        
+        logged_in = self.client.login(username="tester", password="tester")
+        self.assertTrue(logged_in)
+        response = self.client.post("/groups/create/", {
+            "slug": "test",
+            "name": "Test Group",
+            "description": "A test group.",
+        })
+        response = self.client.get("/groups/group/test/")
+        self.assertEqual(BasicGroup.objects.get(slug="test").creator.username, "tester")
+        self.assertEqual(BasicGroup.objects.get(slug="test").members.all()[0].username, "tester")
+        self.assertEqual(response.context[0]["is_member"], True)

apps/basic_groups/urls.py

+from django.conf.urls.defaults import *
+
+from basic_groups.models import BasicGroup
+
+from groups.bridge import ContentBridge
+
+
+bridge = ContentBridge(BasicGroup, 'basic_groups')
+
+
+urlpatterns = patterns('',
+    url(r'^create/$', 'basic_groups.views.create', name="group_create"),
+    url(r'^your_groups/$', 'basic_groups.views.your_groups', name="your_groups"),
+    
+    url(r'^$', 'basic_groups.views.groups', name="group_list"),
+    
+    # group-specific
+    url(r'^group/(?P<group_slug>[-\w]+)/$', 'basic_groups.views.group', name="group_detail"),
+    url(r'^group/(?P<group_slug>[-\w]+)/delete/$', 'basic_groups.views.delete', name="group_delete"),
+)
+
+
+urlpatterns += bridge.include_urls('topics.urls', r'^group/(?P<group_slug>[-\w]+)/topics/')

apps/basic_groups/views.py

+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template import RequestContext
+from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
+from django.core.urlresolvers import reverse
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.models import User
+from django.conf import settings
+
+if "notification" in settings.INSTALLED_APPS:
+    from notification import models as notification
+else:
+    notification = None
+
+from basic_groups.models import BasicGroup
+from basic_groups.forms import BasicGroupForm, BasicGroupUpdateForm
+
+
+@login_required
+def create(request, form_class=BasicGroupForm, template_name="basic_groups/create.html"):
+    
+    group_form = form_class(request.POST or None)
+    
+    if group_form.is_valid():
+        group = group_form.save(commit=False)
+        group.creator = request.user
+        group.save()
+        group.members.add(request.user)
+        group.save()
+        if notification:
+            # @@@ might be worth having a shortcut for sending to all users
+            notification.send(User.objects.all(), "groups_new_group", {"group": group}, queue=True)
+        return HttpResponseRedirect(group.get_absolute_url())
+    
+    return render_to_response(template_name, {
+        "group_form": group_form,
+    }, context_instance=RequestContext(request))
+
+
+def groups(request, template_name="basic_groups/groups.html"):
+    
+    groups = BasicGroup.objects.filter()
+    
+    return render_to_response(template_name, {
+        'groups': groups,
+    }, context_instance=RequestContext(request))
+
+
+def delete(request, group_slug=None, redirect_url=None):
+    group = get_object_or_404(BasicGroup, slug=group_slug)
+    if not redirect_url:
+        redirect_url = reverse('group_list')
+    
+    # @@@ eventually, we'll remove restriction that group.creator can't leave group but we'll still require group.members.all().count() == 1
+    if request.user.is_authenticated() and request.method == "POST" and request.user == group.creator and group.members.all().count() == 1:
+        group.delete()
+        request.user.message_set.create(message="Group %s deleted." % group)
+        # no notification required as the deleter must be the only member
+    
+    return HttpResponseRedirect(redirect_url)
+
+
+@login_required
+def your_groups(request, template_name="basic_groups/your_groups.html"):
+    return render_to_response(template_name, {
+        "groups": BasicGroup.objects.filter(members=request.user).order_by("name"),
+    }, context_instance=RequestContext(request))
+
+
+def group(request, group_slug=None, form_class=BasicGroupUpdateForm, template_name="basic_groups/group.html"):
+    group = get_object_or_404(BasicGroup, slug=group_slug)
+    
+    group_form = form_class(request.POST or None, instance=group)
+    
+    action = request.POST.get('action')
+    
+    if action == "update" and group_form.is_valid():
+        group = group_form.save()
+    elif action == "join":
+        group.members.add(request.user)
+        request.user.message_set.create(message="You have joined the group %s" % group.name)
+        if notification:
+            notification.send([group.creator], "groups_created_new_member", {"user": request.user, "group": group})
+            notification.send(group.members.all(), "groups_new_member", {"user": request.user, "group": group})
+    elif action == "leave":
+        group.members.remove(request.user)
+        request.user.message_set.create(message="You have left the group %s" % group.name)
+        if notification:
+            pass # @@@ no notification on departure yet
+    
+    if not request.user.is_authenticated():
+        is_member = False
+    else:
+        is_member = group.user_is_member(request.user)
+    
+    return render_to_response(template_name, {
+        "group_form": group_form,
+        "group": group,
+        "is_member": is_member,
+    }, context_instance=RequestContext(request))

apps/tag_app/__init__.py

Empty file added.

apps/tag_app/templates/tag_app/tag_list.html

+{% load tagging_tags %}
+{% load theme_tags %}
+
+{% tags_for_object obj as tags %}
+{% if tags %}
+    {% silk "tag_blue" %}
+    {% for tag in tags %}
+        <a href="/tags/{{ tag }}">{{ tag }}</a>
+    {% endfor %}
+{% endif %}

apps/tag_app/templatetags/__init__.py

Empty file added.

apps/tag_app/templatetags/extra_tagging_tags.py

+from django.template import Library
+from django.conf import settings
+
+register = Library()
+
+@register.inclusion_tag("tag_app/tag_list.html")
+def show_tags_for(obj):
+    return {
+        "obj": obj,
+        "MEDIA_URL": settings.MEDIA_URL,
+        "STATIC_URL": settings.STATIC_URL,
+    }
+
+@register.inclusion_tag("tag_app/tag_count_list.html")
+def show_tag_counts(tag_counts):
+    return {"tag_counts": tag_counts}

deploy/__init__.py

Empty file added.

deploy/modpython.py

+
+import os
+import sys
+
+from os.path import abspath, dirname, join
+from site import addsitedir
+
+VIRTUALENV_BASE = "/home/okfn/var/srvc/pinaxdemo/pyenv"
+if not VIRTUALENV_BASE:
+    raise Exception("VIRTUALENV_BASE is not set correctly.")
+
+activate_this = join(VIRTUALENV_BASE, "bin/activate_this.py")
+execfile(activate_this, dict(__file__=activate_this))
+
+from django.core.handlers.modpython import ModPythonHandler
+
+
+class PinaxModPythonHandler(ModPythonHandler):
+    def __call__(self, req):
+        # mod_python fakes the environ, and thus doesn't process SetEnv.
+        # This fixes that. Django will call this again since there is no way
+        # of overriding __call__ to just process the request.
+        os.environ.update(req.subprocess_env)
+        from django.conf import settings
+        
+        sys.path.insert(0, abspath(join(dirname(__file__), "../../")))
+        
+        sys.path.insert(0, join(settings.PINAX_ROOT, "apps"))
+        sys.path.insert(0, join(settings.PROJECT_ROOT, "apps"))
+        
+        return super(PinaxModPythonHandler, self).__call__(req)
+
+
+def handler(req):
+    # mod_python hooks into this function.
+    return PinaxModPythonHandler()(req)

deploy/pinax.fcgi

+# pinax.fcgi is configured to live in projects/community/deploy.
+
+import os
+import sys
+
+from os.path import abspath, dirname, join
+from site import addsitedir
+
+sys.path.insert(0, abspath(join(dirname(__file__), "../../")))
+
+from django.conf import settings
+os.environ["DJANGO_SETTINGS_MODULE"] = "community.settings"
+
+sys.path.insert(0, join(settings.PINAX_ROOT, "apps"))
+sys.path.insert(0, join(settings.PROJECT_ROOT, "apps"))
+
+from django.core.servers.fastcgi import runfastcgi
+runfastcgi(method="threaded", daemonize="false")

deploy/pinax.wsgi

+# pinax.wsgi is configured to live in projects/community/deploy.
+
+import os
+import sys
+
+# redirect sys.stdout to sys.stderr for bad libraries like geopy that uses
+# print statements for optional import exceptions.
+sys.stdout = sys.stderr
+
+from os.path import abspath, dirname, join
+from site import addsitedir
+
+sys.path.insert(0, abspath(join(dirname(__file__), "../../")))
+
+from django.conf import settings
+os.environ["DJANGO_SETTINGS_MODULE"] = "community.settings"
+
+sys.path.insert(0, join(settings.PINAX_ROOT, "apps"))
+sys.path.insert(0, join(settings.PROJECT_ROOT, "apps"))
+
+from django.core.handlers.wsgi import WSGIHandler
+application = WSGIHandler()
+#!/usr/bin/env python
+import sys
+
+from os.path import abspath, dirname, join
+
+try:
+    import pinax
+except ImportError:
+    sys.stderr.write("Error: Can't import Pinax. Make sure you are in a virtual environment that has Pinax installed or create one with pinax-boot.py.\n")
+    sys.exit(1)
+
+from django.conf import settings
+from django.core.management import setup_environ, execute_from_command_line
+
+try:
+    import settings as settings_mod # Assumed to be in the same directory.
+except ImportError:
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+    sys.exit(1)
+
+# setup the environment before we start accessing things in the settings.
+setup_environ(settings_mod)
+
+sys.path.insert(0, join(settings.PINAX_ROOT, "apps"))
+sys.path.insert(0, join(settings.PROJECT_ROOT, "apps"))
+
+if __name__ == "__main__":
+    execute_from_command_line()

media/css/site_tabs.css

+/* SITE-SPECIFIC TAB STYLING */
+
+body.profile #tab_profile a,
+body.groups #tab_groups a,
+body.tribes #tab_tribes a,
+body.projects #tab_projects a,
+body.notices #tab_notices a
+{
+    color: #000; /* selected tab text colour */
+}
+body.profile #tab_profile,
+body.groups #tab_groups,
+body.tribes #tab_tribes,
+body.projects #tab_projects,
+body.notices #tab_notices
+{
+    margin: 0; /* to compensate for border */
+    padding: 5px 0 5px;
+    background-color: #DEF; /* selected tab colour */
+    border-left: 1px solid #000; /* tab border */
+    border-top: 1px solid #000; /* tab border */
+    border-right: 1px solid #000; /* tab border */
+}
+--find-links=http://pypi.pinaxproject.com
+--find-links=http://pypi2.pinaxproject.com
+
+python-dateutil==1.4.1
+python-openid==2.2.4
+python-yadis==1.1.0
+simplejson==2.0.9
+pytz==2009l
+docutils==0.5
+smartypants==1.6.0.3
+Markdown==2.0.1
+textile==2.1.3
+diff-match-patch==20090804
+django-atomformat==0.1.0dev
+creole==1.0.1
+
+django-notification==0.1.4
+django-openid==0.2.0
+django-email-confirmation==0.1.3
+django-mailer==0.1.0
+django-announcements==0.1.0
+django-pagination==1.0.5.1
+django-timezones==0.1.4
+django-bookmarks==0.1.0
+django-ajax-validation==0.1.3
+django-uni-form==0.6.0
+django-wikiapp==0.2.0
+django-tagging==0.3
+django-avatar==1.0.2
+django-threadedcomments==0.5.1
+django-gravatar==0.1.0
+django-sorting>=0.1
+django-filter==0.5.1
+template_utils==0.4p2
+django-staticfiles==0.1.2
+# -*- coding: utf-8 -*-
+# Django settings for group project.
+
+import os.path
+import posixpath
+import pinax
+
+PINAX_ROOT = os.path.abspath(os.path.dirname(pinax.__file__))
+PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
+
+# tells Pinax to use the default theme
+PINAX_THEME = 'default'
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+# tells Pinax to serve media through django.views.static.serve.
+SERVE_MEDIA = DEBUG
+
+ADMINS = (
+    # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'sqlite3'    # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
+DATABASE_NAME = 'dev.db'       # Or path to database file if using sqlite3.
+DATABASE_USER = ''             # Not used with sqlite3.
+DATABASE_PASSWORD = ''         # Not used with sqlite3.
+DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
+
+# Local time zone for this installation. Choices can be found here:
+# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
+# although not all variations may be possible on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'US/Eastern'
+
+# Language code for this installation. All choices can be found here:
+# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
+# http://blogs.law.harvard.edu/tech/stories/storyReader$15
+LANGUAGE_CODE = 'en'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'site_media', 'media')
+
+# URL that handles the media served from MEDIA_ROOT.
+# Example: "http://media.lawrence.com"
+MEDIA_URL = '/site_media/media/'
+
+# Absolute path to the directory that holds static files like app media.
+# Example: "/home/media/media.lawrence.com/apps/"
+STATIC_ROOT = os.path.join(PROJECT_ROOT, 'site_media', 'static')
+
+# URL that handles the static files like app media.
+# Example: "http://media.lawrence.com"
+STATIC_URL = '/site_media/static/'
+
+# Additional directories which hold static files
+STATICFILES_DIRS = (
+    ('community', os.path.join(PROJECT_ROOT, 'media')),
+    ('pinax', os.path.join(PINAX_ROOT, 'media', PINAX_THEME)),
+)
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = posixpath.join(STATIC_URL, "admin/")
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '#ii34$z9a)d0ok!q(()%v#62s!9(@z4066vxg)5h*v2rsy)dya'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.load_template_source',
+    'django.template.loaders.app_directories.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django_openid.consumer.SessionConsumer',
+    'account.middleware.LocaleMiddleware',
+    'django.middleware.doc.XViewMiddleware',
+    'pagination.middleware.PaginationMiddleware',
+    'django_sorting.middleware.SortingMiddleware',
+    'pinax.middleware.security.HideSensistiveFieldsMiddleware',
+)
+
+ROOT_URLCONF = 'community.urls'
+
+TEMPLATE_DIRS = (
+    os.path.join(PROJECT_ROOT, "templates"),
+    os.path.join(PINAX_ROOT, "templates", PINAX_THEME),
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+    "django.core.context_processors.auth",
+    "django.core.context_processors.debug",
+    "django.core.context_processors.i18n",
+    "django.core.context_processors.media",
+    "django.core.context_processors.request",
+    
+    "pinax.core.context_processors.pinax_settings",
+    
+    "notification.context_processors.notification",
+    "announcements.context_processors.site_wide_announcements",
+    "account.context_processors.openid",
+    "account.context_processors.account",
+)
+
+INSTALLED_APPS = (
+    # included
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django.contrib.humanize',
+    'pinax.templatetags',
+    
+    # external
+    'notification', # must be first
+    'django_openid',
+    'emailconfirmation',
+    'mailer',
+    'announcements',
+    'pagination',
+    'timezones',
+    'ajax_validation',
+    'tagging',
+    'uni_form',
+    'wiki',
+    'avatar',
+    'threadedcomments',
+    'tribes',
+    'projects',
+    'gravatar',
+    'django_sorting',
+    'photologue',
+    'attachments',
+    'django_markup',
+    'django_filters',
+    'staticfiles',
+    
+    # internal (for now)
+    'basic_profiles',
+    'account',
+    'signup_codes',
+    'tag_app',
+    'tagging_utils',
+    'threadedcomments_extras',
+    'groups',
+    
+    'topics',
+    'tasks',
+    'photos',
+    
+    'basic_groups',
+    
+    'about',
+    
+    'django.contrib.admin',
+)
+
+ABSOLUTE_URL_OVERRIDES = {
+    "auth.user": lambda o: "/profiles/profile/%s/" % o.username,
+}
+
+MARKUP_FILTER_FALLBACK = 'none'
+MARKUP_CHOICES = (
+    ('restructuredtext', u'reStructuredText'),
+    ('textile', u'Textile'),
+    ('markdown', u'Markdown'),
+    ('creole', u'Creole'),
+)
+WIKI_MARKUP_CHOICES = MARKUP_CHOICES
+
+AUTH_PROFILE_MODULE = 'basic_profiles.Profile'
+NOTIFICATION_LANGUAGE_MODULE = 'account.Account'
+
+ACCOUNT_OPEN_SIGNUP = True
+ACCOUNT_REQUIRED_EMAIL = False
+ACCOUNT_EMAIL_VERIFICATION = False
+
+EMAIL_CONFIRMATION_DAYS = 2
+EMAIL_DEBUG = DEBUG
+CONTACT_EMAIL = "feedback@example.com"
+SITE_NAME = "Pinax"
+LOGIN_URL = "/account/login/"
+LOGIN_REDIRECT_URLNAME = "what_next"
+
+# local_settings.py can be used to override environment-specific settings
+# like database and email that differ between development and production.
+try:
+    from local_settings import *
+except ImportError:
+    pass

templates/about/what_next.html

+{% extends "site_base.html" %}
+
+{% load i18n %}
+{% load ifsetting_tag %}
+
+{% block head_title %}{% trans "What Next?" %}{% endblock %}
+
+{% block body %}
+    <h1>{% trans "What Next?" %}</h1>
+    
+    {% if user.is_authenticated %}
+        <p>Here are some things to do to get started with this site:</p>
+        
+        <dl class="what_next">
+            <dt><a href="{% url acct_email %}">verify an email address</a></dt>
+            <dd>so you can receive notifications, reset your password and so people can find you more easily.</dd>
+            
+            <dt><a href="{% url profile_detail user %}">fill out your profile</a></dt>
+            <dd>to tell the world a little about yourself.</dd>
+            
+            {% ifsetting ACCOUNT_OPEN_SIGNUP %}
+            {% else %}
+                {% if user.is_staff %}
+                    <dt><a href="{% url admin_invite_user %}">invite more people to the site</a> [admin only]</dt>
+                    <dd>so more people can share in the fun.</dd>
+                {% endif %}
+            {% endifsetting %}
+        </dl>
+        {% else %}
+            {% url acct_login as login_url %}
+            
+            <p class="what_next">
+                {% ifsetting ACCOUNT_OPEN_SIGNUP %}
+                    {% url acct_signup as signup_url %}
+                    {% blocktrans %}Start by <a href="{{ signup_url }}">signing up</a> and <a href="{{ login_url }}">logging in</a>.{% endblocktrans %}
+                {% else %}
+                    {% blocktrans %}Start by <a href="{{ login_url }}">logging in</a>.{% endblocktrans %}
+                {% endifsetting %}
+            </p>
+        {% endif %}
+{% endblock %}

templates/account/base.html

+{% extends "site_base.html" %}
+
+{% load i18n %}
+
+{% block rtab_id %}id="account_tab"{% endblock %}
+
+{% block subnav %}
+    {% if user.is_authenticated %}
+        <ul>
+            <li><a href="{% url acct_email %}">{% trans "E-Mail Addresses" %}</a></li>
+            <li><a href="/openid/associations/">{% trans "OpenID Associations" %}</a></li>
+            {% if user.password %}
+            <li><a href="{% url acct_passwd %}">{% trans "Change Password" %}</a></li>
+            {% else %}
+            <li><a href="{% url acct_passwd_set %}">{% trans "Set Password" %}</a></li>
+            {% endif %}
+            {% if user.password and request.openids %}
+            <li><a href="{% url acct_passwd_delete %}">{% trans "Delete Password" %}</a></li>
+            {% endif %}
+            <li><a href="{% url acct_language_change %}">{% trans "Language" %}</a></li>
+            <li><a href="{% url acct_timezone_change %}">{% trans "Timezone" %}</a></li>
+        </ul>
+    {% else %}
+        &nbsp; {# need this to give subnav some height when empty #}
+    {% endif %}
+{% endblock %}

templates/homepage.html

+{% extends "site_base.html" %}
+
+{% load i18n %}
+{% load ifsetting_tag %}
+
+{% block head_title %}{% trans "Welcome" %}{% endblock %}
+
+{% block tab_id %}id="home_tab"{% endblock %}
+
+{% block body %}
+    <h1>{% trans "Welcome to Pinax" %}</h1>
+    
+    <p>
+        {% blocktrans %}
+        <b>Pinax</b> is a <a href="http://djangoproject.com/">Django</a>
+        project intended to provide a starting point for websites. By
+        integrating numerous reusable Django apps to take care of the
+        things that many sites have in common, it lets you focus on what
+        makes your site different.
+        {% endblocktrans %}
+    </p>
+    
+    <p>
+        {% blocktrans %}
+        For more information about Pinax, see
+        <a href="http://pinaxproject.com/">http://pinaxproject.com/</a>.
+        {% endblocktrans %}
+    </p>
+    
+    <h2>{% trans "About this project" %}</h2>
+    
+    <p>
+        {% blocktrans %}
+        This project demonstrates group functionality with a barebones group
+        containing no extra content apps as well as two additional group types,
+        tribes and projects, which show different membership approaches and
+        content apps.
+        {% endblocktrans %}
+    </p>
+    
+    <hr />
+    {% if user.is_authenticated %}
+        {% url what_next as what_next_url %}
+        <p class="what_next">{% blocktrans %}Wondering <a href="{{ what_next_url }}">What Next</a>?{% endblocktrans %}</p>
+    {% else %}
+        {% url acct_login as login_url %}
+        {% ifsetting ACCOUNT_OPEN_SIGNUP %}
+            {% url acct_signup as signup_url %}
+            <p>{% blocktrans %}You can <a href="{{ signup_url }}">Sign up</a> and <a href="{{ login_url }}">Log in</a> to try out the site.{% endblocktrans %}</p>
+        {% else %}
+            <p>{% blocktrans %}You can <a href="{{ login_url }}">Log in</a> to try out the site.{% endblocktrans %}</p>
+        {% endifsetting %}
+    {% endif %}
+    
+{% endblock %}

templates/site_base.html

+{% extends "base.html" %}
+
+{% load i18n %}
+{% load ifsetting_tag %}
+{% load openid_tags %}
+
+{% block extra_head_base %}
+    <link rel="stylesheet" href="{{ STATIC_URL }}css/site_tabs.css" />
+    <link rel="stylesheet" href="{{ STATIC_URL }}tasks/css/tasks.css" />
+    <link rel="stylesheet" href="{{ STATIC_URL }}pinax/css/groups.css" />
+    {% block extra_head %}{% endblock %}
+{% endblock %}
+
+{% block login %}
+    {% if user.is_authenticated %}
+        {% openid_icon openid user %}<b>{{ user }}</b> | <a href="{% url acct_email %}">{% trans "Account" %}</a> | {% if user.is_staff %}<a href="/admin/">{% trans "Admin" %}</a> | {% endif %}<a href="/openid/logout/?next={% url acct_logout %}">{% trans "Logout" %}</a>
+    {% else %}
+        <a href="{% url acct_login %}">{% trans "Login" %}</a> {% ifsetting ACCOUNT_OPEN_SIGNUP %}{% trans "or" %} <a href="{% url acct_signup %}"><b>{% trans "Sign up" %}</b></a>{% endifsetting %}
+    {% endif %}
+{% endblock %}
+
+{% block logo_link_image %}<a href="{% url home %}"><img src="{{ STATIC_URL }}pinax/images/logo.png" alt="Pinax"/></a>{% endblock %}
+
+{% block right_tabs %}
+    {% if user.is_authenticated %}
+        <ul class="tabs">{% spaceless %}
+            <li id="tab_profile"><a href="{% url profile_detail user %}">{% trans "Profile" %}</a></li>
+            <li id="tab_groups"><a href="{% url group_list %}">{% trans "Groups" %}</a></li>
+            <li id="tab_tribes"><a href="{% url tribe_list %}">{% trans "Tribes" %}</a></li>
+            <li id="tab_projects"><a href="{% url project_list %}">{% trans "Project" %}</a></li>
+            <li id="tab_notices"><a href="{% url notification_notices %}">{% trans "Notices" %}{% if notice_unseen_count %} ({{ notice_unseen_count }}){% endif %}</a></li>
+        {% endspaceless %}</ul>
+    {% endif %}
+{% endblock %}
+
+{% block footer %}
+<div class="legal">
+    {% trans "&copy; 2008 &lt;your company here&gt;" %}
+    - <a href="{% url about %}">{% trans "About" %}</a>
+    - <a href="{% url terms %}">{% trans "Terms of Service" %}</a>
+    - <a href="{% url privacy %}">{% trans "Privacy Policy" %}</a>
+    - <a href="{% url dmca %}">{% trans "DMCA Notice" %}</a>
+</div>
+{% endblock %}
+
+{% block extra_body_base %}
+    <script src="{{ STATIC_URL }}tasks/js/tasks.js" type="text/javascript"></script>
+    {% block extra_body %}{% endblock %}
+{% endblock %}
+from django.conf.urls.defaults import *
+from django.conf import settings
+
+from django.views.generic.simple import direct_to_template
+
+from django.contrib import admin
+admin.autodiscover()
+
+from account.openid_consumer import PinaxConsumer
+
+
+if settings.ACCOUNT_OPEN_SIGNUP:
+    signup_view = "account.views.signup"
+else:
+    signup_view = "signup_codes.views.signup"
+
+
+urlpatterns = patterns('',
+    url(r'^$', direct_to_template, {
+        "template": "homepage.html",
+    }, name="home"),
+    
+    url(r'^admin/invite_user/$', 'signup_codes.views.admin_invite_user', name="admin_invite_user"),
+    url(r'^account/signup/$', signup_view, name="acct_signup"),
+    
+    (r'^about/', include('about.urls')),
+    (r'^account/', include('account.urls')),
+    (r'^openid/(.*)', PinaxConsumer()),
+    (r'^profiles/', include('basic_profiles.urls')),
+    (r'^notices/', include('notification.urls')),
+    (r'^announcements/', include('announcements.urls')),
+    (r'^tagging_utils/', include('tagging_utils.urls')),
+    (r'^comments/', include('threadedcomments.urls')),
+    (r'^attachments/', include('attachments.urls')),
+    
+    (r'^groups/', include('basic_groups.urls')),
+    (r'^tribes/', include('tribes.urls')),
+    (r'^projects/', include('projects.urls')),
+    
+    (r'^admin/(.*)', admin.site.root),
+)
+
+if settings.SERVE_MEDIA:
+    urlpatterns += patterns('',
+        (r'^site_media/', include('staticfiles.urls')),
+    )