Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8d1492f9b3 | ||
![]() |
05dee45ee8 | ||
![]() |
60aedff443 | ||
![]() |
0863c41293 | ||
![]() |
07921966b8 | ||
![]() |
4d1c1ce952 | ||
![]() |
3288c2af78 | ||
![]() |
ca6062f5af | ||
![]() |
f3cfc80f15 | ||
![]() |
ef03c3fa74 | ||
![]() |
cda56dba20 | ||
![]() |
104cf2a32a | ||
![]() |
c91028d840 | ||
![]() |
a1b7f2d9fa |
14
CHANGELOG
Normal file
14
CHANGELOG
Normal file
@ -0,0 +1,14 @@
|
||||
0.2.1 - December 9 2009
|
||||
=======================
|
||||
- disallow submission of empty ideas
|
||||
- fix a packaging bug
|
||||
|
||||
0.2.0 - August 25 2009
|
||||
======================
|
||||
- rewrite for Sunlight Labs website
|
||||
- reduced a few extra queries
|
||||
- integrate with django-gatekeeper if installed
|
||||
|
||||
0.1.0 - May 27 2009
|
||||
=====
|
||||
- initial working release, focused on subsites
|
2
LICENSE
2
LICENSE
@ -1,3 +1,4 @@
|
||||
Copyright (c) 2015, James Turk
|
||||
Copyright (c) 2009, Sunlight Foundation
|
||||
All rights reserved.
|
||||
|
||||
@ -5,6 +6,5 @@ Redistribution and use in source and binary forms, with or without modification,
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Sunlight Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
@ -1,4 +1,5 @@
|
||||
include LICENSE
|
||||
include CHANGELOG
|
||||
include README.rst
|
||||
include *.py
|
||||
recursive-include brainstorm/templates *
|
||||
|
13
README.rst
13
README.rst
@ -4,21 +4,14 @@ django-brainstorm
|
||||
|
||||
Django app for creating a site with multiple areas to brainstorm ideas.
|
||||
|
||||
This app powers http://feedback.sunlightfoundation.com/hackathon/ and http://feedback.sunlightfoundation.com/oogl/ and makes it easy to create any number of these 'subsites.'
|
||||
|
||||
django-brainstorm is a project of Sunlight Labs (c) 2009.
|
||||
Written by James Turk <jturk@sunlightfoundation.com>.
|
||||
|
||||
All code is under a BSD-style license, see LICENSE for details.
|
||||
|
||||
Source: http://github.com/sunlightlabs/django-brainstorm/
|
||||
Source: http://github.com/jamesturk/django-brainstorm/
|
||||
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
python >= 2.4
|
||||
django >= 1.0
|
||||
* python >= 2.4
|
||||
* django >= 1.0
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
@ -0,0 +1 @@
|
||||
__version__ = '0.2.1'
|
@ -3,25 +3,29 @@ from brainstorm.models import Subsite
|
||||
|
||||
class SubsiteFeed(Feed):
|
||||
|
||||
description_template = 'feedback/idea_rss_description.html'
|
||||
title_template = 'brainstorm/feed_title.html'
|
||||
description_template = 'brainstorm/feed_description.html'
|
||||
|
||||
def get_object(self, bits):
|
||||
return Subsite.objects.get(slug__exact=bits[0])
|
||||
|
||||
def title(self, obj):
|
||||
return '%s' % obj.name
|
||||
return 'Latest ideas submitted for %s' % obj.name
|
||||
|
||||
def description(self, obj):
|
||||
return 'Latest ideas submitted for %s' % obj.name
|
||||
|
||||
def link(self, obj):
|
||||
if not obj:
|
||||
raise FeedDoesNotExist
|
||||
return obj.get_absolute_url()
|
||||
|
||||
def description(self, obj):
|
||||
return 'Latest ideas submitted for %s' % obj.name
|
||||
|
||||
def items(self, obj):
|
||||
return obj.ideas.order_by('-submit_date')[:30]
|
||||
|
||||
def item_link(self, item):
|
||||
return item.get_absolute_url()
|
||||
|
||||
def item_author_name(self, item):
|
||||
return item.user
|
||||
|
||||
|
@ -3,7 +3,7 @@ from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.contenttypes import generic
|
||||
from django.contrib.comments.models import Comment
|
||||
import secretballot
|
||||
from django.db.models.signals import post_save
|
||||
|
||||
ALLOW_ALL, REQUIRE_LOGIN, DISALLOW_ALL = range(3)
|
||||
SUBSITE_POST_STATUS = (
|
||||
@ -26,7 +26,7 @@ class Subsite(models.Model):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('subsite', args=[self.slug])
|
||||
return reverse('ideas_popular', args=[self.slug])
|
||||
|
||||
def user_can_post(self, user):
|
||||
if self.post_status == DISALLOW_ALL:
|
||||
@ -36,10 +36,16 @@ class Subsite(models.Model):
|
||||
elif self.post_status == REQUIRE_LOGIN:
|
||||
return not user.is_anonymous()
|
||||
|
||||
class IdeaManager(models.Manager):
|
||||
|
||||
def with_user_vote(self, user):
|
||||
return self.extra(select={'user_vote':'SELECT value FROM brainstorm_vote WHERE idea_id=brainstorm_idea.id AND user_id=%s'}, select_params=[user.id])
|
||||
|
||||
class Idea(models.Model):
|
||||
|
||||
title = models.CharField(max_length=100)
|
||||
description = models.TextField()
|
||||
score = models.IntegerField(default=0)
|
||||
|
||||
submit_date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@ -48,10 +54,28 @@ class Idea(models.Model):
|
||||
|
||||
comments = generic.GenericRelation(Comment, object_id_field='object_pk')
|
||||
|
||||
objects = IdeaManager()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('idea', args=[self.subsite_id, self.id])
|
||||
return reverse('idea_detail', args=[self.subsite_id, self.id])
|
||||
|
||||
secretballot.enable_voting_on(Idea)
|
||||
class Vote(models.Model):
|
||||
user = models.ForeignKey(User, related_name='idea_votes')
|
||||
idea = models.ForeignKey(Idea, related_name='votes')
|
||||
value = models.IntegerField()
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s %s on %s' % (self.user, self.value, self.idea)
|
||||
|
||||
class Meta:
|
||||
unique_together = (('user', 'idea'),)
|
||||
|
||||
def update_idea_votes(sender, instance, created, **kwargs):
|
||||
score = instance.idea.votes.aggregate(score=models.Sum('value'))['score']
|
||||
instance.idea.score = score
|
||||
instance.idea.save()
|
||||
post_save.connect(update_idea_votes, sender=Vote)
|
||||
|
@ -1,8 +1,11 @@
|
||||
from django.conf.urls.defaults import *
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from brainstorm.models import Idea
|
||||
from brainstorm.feeds import SubsiteFeed
|
||||
|
||||
BRAINSTORM_USE_SECRETBALLOT = getattr(settings, 'BRAINSTORM_USE_SECRETBALLOT', False)
|
||||
|
||||
feeds = {
|
||||
'latest': SubsiteFeed,
|
||||
}
|
||||
@ -14,14 +17,15 @@ urlpatterns = patterns('',
|
||||
)
|
||||
|
||||
urlpatterns += patterns('brainstorm.views',
|
||||
url(r'^submit_comment/$', 'submit_comment', name='submit_idea_comment'),
|
||||
url(r'^(?P<slug>[\w-]+)/$', 'idea_list', {'ordering': 'most_popular'}, name='subsite'),
|
||||
url(r'^(?P<slug>[\w-]+)/latest/$', 'idea_list', {'ordering': 'latest'}, name='subsite_latest'),
|
||||
url(r'^(?P<slug>[\w-]+)/(?P<id>\d+)/$', 'idea_detail', name='idea'),
|
||||
url(r'^(?P<slug>[\w-]+)/$', 'idea_list', {'ordering': 'most_popular'}, name='ideas_popular'),
|
||||
url(r'^(?P<slug>[\w-]+)/latest/$', 'idea_list', {'ordering': 'latest'}, name='ideas_latest'),
|
||||
url(r'^(?P<slug>[\w-]+)/(?P<id>\d+)/$', 'idea_detail', name='idea_detail'),
|
||||
url(r'^(?P<slug>[\w-]+)/new_idea/$', 'new_idea', name='new_idea'),
|
||||
url(r'^vote/$', 'vote', name='idea_vote'),
|
||||
)
|
||||
|
||||
urlpatterns = patterns('secretballot.views',
|
||||
if BRAINSTORM_USE_SECRETBALLOT:
|
||||
urlpatterns = patterns('secretballot.views',
|
||||
url(r'^vote_up/(?P<object_id>\d+)/$', 'vote',
|
||||
{'content_type': ContentType.objects.get_for_model(Idea), 'vote': 1},
|
||||
name='vote_up'),
|
||||
@ -31,5 +35,5 @@ urlpatterns = patterns('secretballot.views',
|
||||
url(r'^unvote/(?P<object_id>\d+)/$', 'vote',
|
||||
{'content_type': ContentType.objects.get_for_model(Idea), 'vote': 0},
|
||||
name='unvote'),
|
||||
) + urlpatterns
|
||||
) + urlpatterns
|
||||
|
||||
|
@ -1,77 +1,65 @@
|
||||
import datetime
|
||||
from django.template import RequestContext
|
||||
from django.core.paginator import Paginator, InvalidPage, EmptyPage
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import get_object_or_404, render_to_response
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, render_to_response, redirect
|
||||
from django.http import HttpResponse
|
||||
from django.contrib.comments.models import Comment
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.views.generic import list_detail
|
||||
from django.views.decorators.http import require_POST
|
||||
from brainstorm.models import Subsite, Idea
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.conf import settings
|
||||
from brainstorm.models import Subsite, Idea, Vote
|
||||
|
||||
def idea_list(request, slug, ordering='-total_upvotes'):
|
||||
subsite = get_object_or_404(Subsite, pk=slug)
|
||||
ordering_db = {'most_popular': '-total_upvotes',
|
||||
ordering_db = {'most_popular': '-score',
|
||||
'latest': '-submit_date'}[ordering]
|
||||
paginator = Paginator(Idea.objects.from_request(request).filter(subsite=subsite).order_by(ordering_db),
|
||||
subsite.ideas_per_page)
|
||||
|
||||
try:
|
||||
page = int(request.GET.get('page', '1'))
|
||||
except ValueError:
|
||||
page = 1
|
||||
|
||||
try:
|
||||
ideas = paginator.page(page)
|
||||
except (EmptyPage, InvalidPage):
|
||||
ideas = paginator.page(paginator.num_pages)
|
||||
|
||||
return render_to_response('brainstorm/index.html',
|
||||
{'subsite':subsite, 'ideas': ideas,
|
||||
'ordering': ordering,
|
||||
'user_can_post': subsite.user_can_post(request.user)},
|
||||
context_instance=RequestContext(request))
|
||||
qs = Idea.objects.with_user_vote(request.user).filter(subsite__slug=slug).select_related().order_by(ordering_db)
|
||||
if hasattr(qs, '_gatekeeper'):
|
||||
qs = qs.approved()
|
||||
return list_detail.object_list(request, queryset=qs,
|
||||
extra_context={'ordering': ordering, 'subsite':slug}, paginate_by=10,
|
||||
template_object_name='idea')
|
||||
|
||||
def idea_detail(request, slug, id):
|
||||
subsite = get_object_or_404(Subsite, pk=slug)
|
||||
idea = get_object_or_404(Idea.objects.from_request(request),
|
||||
subsite=slug, pk=id)
|
||||
|
||||
return render_to_response('brainstorm/idea.html',
|
||||
{'subsite':subsite, 'idea': idea,
|
||||
'user_can_post': subsite.user_can_post(request.user)},
|
||||
idea = get_object_or_404(Idea.objects.with_user_vote(request.user), pk=id, subsite__slug=slug)
|
||||
return render_to_response('brainstorm/idea_detail.html',
|
||||
{'idea': idea},
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@require_POST
|
||||
def new_idea(request, slug):
|
||||
subsite = get_object_or_404(Subsite, pk=slug)
|
||||
if not subsite.user_can_post(request.user):
|
||||
return HttpResponseRedirect(subsite.get_absolute_url())
|
||||
return redirect(subsite.get_absolute_url())
|
||||
title = request.POST['title']
|
||||
description = request.POST['description']
|
||||
if request.user.is_anonymous():
|
||||
user = None
|
||||
else:
|
||||
if not title.strip() or not description.strip():
|
||||
return redirect(subsite.get_absolute_url())
|
||||
user = request.user
|
||||
idea = Idea.objects.create(title=title, description=description,
|
||||
user=user, subsite=subsite)
|
||||
return HttpResponseRedirect(idea.get_absolute_url())
|
||||
idea = Idea.objects.create(title=title, description=description, user=user,
|
||||
subsite=subsite)
|
||||
return redirect(idea)
|
||||
|
||||
@require_POST
|
||||
def submit_comment(request):
|
||||
from django.conf import settings
|
||||
content_type = ContentType.objects.get_for_model(Idea).id
|
||||
site = settings.SITE_ID
|
||||
object_pk = request.POST['idea_id']
|
||||
name = request.POST.get('name', 'anonymous')
|
||||
email = request.POST.get('email', '')
|
||||
url = request.POST.get('url', '')
|
||||
comment = request.POST['comment']
|
||||
date = datetime.datetime.now()
|
||||
ip = request.META['REMOTE_ADDR']
|
||||
c = Comment.objects.create(user_name=name, user_email=email, user_url=url,
|
||||
comment=comment, submit_date=date, ip_address=ip,
|
||||
site_id=site, content_type_id=content_type, object_pk=object_pk)
|
||||
idea = Idea.objects.get(pk=object_pk)
|
||||
linkback = '%s#c%s' % (idea.get_absolute_url(), c.id)
|
||||
return HttpResponseRedirect(linkback)
|
||||
@login_required
|
||||
def vote(request):
|
||||
idea_id = int(request.POST.get('idea'))
|
||||
score = int(request.POST.get('score'))
|
||||
if score not in (0,1):
|
||||
score = 0
|
||||
idea = get_object_or_404(Idea, pk=idea_id)
|
||||
score_diff = score
|
||||
vote, created = Vote.objects.get_or_create(user=request.user, idea=idea,
|
||||
defaults={'value':score})
|
||||
if not created:
|
||||
new_score = idea.score + (score-vote.value)
|
||||
vote.value = score
|
||||
vote.save()
|
||||
else:
|
||||
new_score = idea.score
|
||||
|
||||
if request.is_ajax():
|
||||
return HttpResponse("{'score':%d}" % new_score)
|
||||
|
||||
return redirect(idea)
|
||||
|
6
setup.py
6
setup.py
@ -4,15 +4,15 @@ long_description = open('README.rst').read()
|
||||
|
||||
setup(
|
||||
name='django-brainstorm',
|
||||
version="0.1",
|
||||
version="0.2.1",
|
||||
package_dir={'brainstorm': 'brainstorm'},
|
||||
packages=['brainstorm'],
|
||||
package_data={'brainstorm': ['templates/brainstorm/*.html']},
|
||||
description='Django brainstorming site',
|
||||
author='James Turk',
|
||||
author_email='jturk@sunlightfoundation.com',
|
||||
author_email='james.p.turk@gmail.com',
|
||||
license='BSD License',
|
||||
url='http://github.com/sunlightlabs/django-brainstorm/',
|
||||
url='http://github.com/jamesturk/django-brainstorm/',
|
||||
long_description=long_description,
|
||||
platforms=["any"],
|
||||
classifiers=[
|
||||
|
Loading…
Reference in New Issue
Block a user