add poll feature ref as squash commit see also http://support.djangobb.org/topic/333/ (This is a combination of 9 commits.)

This commit is contained in:
JensDiemer 2012-08-10 11:12:52 +03:00
parent 725fecaedf
commit c67ed01f3b
13 changed files with 668 additions and 147 deletions
.gitignore
djangobb_forum

13
.gitignore vendored Normal file
View file

@ -0,0 +1,13 @@
*.py[co]
*.egg-info
.pydevproject
.project
.settings
*.db
*.json
*.log
*~
local_settings.py
.env
build/
dist/

View file

@ -6,7 +6,7 @@ from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from djangobb_forum.models import Category, Forum, Topic, Post, Profile, Reputation, \
Report, Ban, Attachment
Report, Ban, Attachment, Poll, PollChoice
class BaseModelAdmin(admin.ModelAdmin):
@ -71,9 +71,21 @@ class AttachmentAdmin(BaseModelAdmin):
list_filter = ("content_type",)
admin.site.unregister(User)
class PollChoiceInline(admin.TabularInline):
model = PollChoice
extra = 3
class PollAdmin(admin.ModelAdmin):
list_display = ("question", "active",)
list_display_links = ("question",)
list_editable = ("active",)
list_filter = ("active",)
inlines = [PollChoiceInline]
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
admin.site.register(Category, CategoryAdmin)
admin.site.register(Forum, ForumAdmin)
admin.site.register(Topic, TopicAdmin)
@ -83,4 +95,5 @@ admin.site.register(Reputation, ReputationAdmin)
admin.site.register(Report, ReportAdmin)
admin.site.register(Ban, BanAdmin)
admin.site.register(Attachment, AttachmentAdmin)
admin.site.register(Poll, PollAdmin)

View file

@ -1,14 +1,16 @@
# -*- coding: utf-8 -*-
# coding: utf-8
import os.path
from datetime import datetime
from datetime import datetime, timedelta
from django import forms
from django.conf import settings
from django.contrib.auth.models import User
from django.db.models.expressions import F
from django.utils.translation import ugettext_lazy as _
from djangobb_forum.models import Topic, Post, Profile, Reputation, Report, \
Attachment
Attachment, Poll, PollChoice
from djangobb_forum import settings as forum_settings
from djangobb_forum.util import convert_text_to_html, set_language
@ -44,6 +46,8 @@ SEARCH_IN_CHOICES = (
class AddPostForm(forms.ModelForm):
FORM_NAME = "AddPostForm" # used in view and template submit button
name = forms.CharField(label=_('Subject'), max_length=255,
widget=forms.TextInput(attrs={'size':'115'}))
attachment = forms.FileField(label=_('Attachment'), required=False)
@ -293,20 +297,20 @@ class UserSearchForm(forms.Form):
sort_by = self.cleaned_data['sort_by']
sort_dir = self.cleaned_data['sort_dir']
qs = qs.filter(username__contains=username, forum_profile__post_count__gte=forum_settings.POST_USER_SEARCH)
if sort_by=='username':
if sort_dir=='ASC':
if sort_by == 'username':
if sort_dir == 'ASC':
return qs.order_by('username')
elif sort_dir=='DESC':
elif sort_dir == 'DESC':
return qs.order_by('-username')
elif sort_by=='registered':
if sort_dir=='ASC':
elif sort_by == 'registered':
if sort_dir == 'ASC':
return qs.order_by('date_joined')
elif sort_dir=='DESC':
elif sort_dir == 'DESC':
return qs.order_by('-date_joined')
elif sort_by=='num_posts':
if sort_dir=='ASC':
elif sort_by == 'num_posts':
if sort_dir == 'ASC':
return qs.order_by('forum_profile__post_count')
elif sort_dir=='DESC':
elif sort_dir == 'DESC':
return qs.order_by('-forum_profile__post_count')
else:
return qs
@ -401,3 +405,83 @@ class ReportForm(forms.ModelForm):
if commit:
report.save()
return report
class VotePollForm(forms.Form):
"""
Dynamic form for the poll.
"""
FORM_NAME = "VotePollForm" # used in view and template submit button
choice = forms.MultipleChoiceField()
def __init__(self, poll, *args, **kwargs):
self.poll = poll
super(VotePollForm, self).__init__(*args, **kwargs)
choices = self.poll.choices.all().values_list("id", "choice")
if self.poll.choice_count == 1:
self.fields["choice"] = forms.ChoiceField(
choices=choices, widget=forms.RadioSelect
)
else:
self.fields["choice"] = forms.MultipleChoiceField(
choices=choices, widget=forms.CheckboxSelectMultiple
)
def clean_choice(self):
ids = self.cleaned_data["choice"]
count = len(ids)
if count > self.poll.choice_count:
raise forms.ValidationError(
_(u'You have selected too many choices! (Only %i allowed.)') % self.poll.choice_count
)
return ids
class PollForm(forms.ModelForm):
answers = forms.CharField(min_length=2, widget=forms.Textarea,
help_text=_("Write each answer on a new line.")
)
days = forms.IntegerField(required=False, min_value=1,
help_text=_("Number of days for this poll to run. Leave empty for never ending poll.")
)
class Meta:
model = Poll
fields = ['question', 'choice_count']
def create_poll(self):
"""
return True if one field filled with data -> the user wants to create a poll
"""
return any(self.data.get(key) for key in ('question', 'answers', 'days'))
def clean_answers(self):
# validate if there is more than whitespaces ;)
raw_answers = self.cleaned_data["answers"]
answers = [answer.strip() for answer in raw_answers.splitlines() if answer.strip()]
if len(answers) == 0:
raise forms.ValidationError(_(u"There is no valid answer!"))
# validate length of all answers
is_max_length = max([len(answer) for answer in answers])
should_max_length = PollChoice._meta.get_field("choice").max_length
if is_max_length > should_max_length:
raise forms.ValidationError(_(u"One of this answers are too long!"))
return answers
def save(self, post):
"""
Create poll and all answers in PollChoice model.
"""
poll = super(PollForm, self).save(commit=False)
poll.topic = post.topic
days = self.cleaned_data["days"]
if days:
now = datetime.now()
poll.deactivate_date = now + timedelta(days=days)
poll.save()
answers = self.cleaned_data["answers"]
for answer in answers:
PollChoice.objects.create(poll=poll, choice=answer)

View file

@ -0,0 +1,226 @@
# -*- coding: 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 'PollChoice'
db.create_table('djangobb_forum_pollchoice', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('poll', self.gf('django.db.models.fields.related.ForeignKey')(related_name='choices', to=orm['djangobb_forum.Poll'])),
('choice', self.gf('django.db.models.fields.CharField')(max_length=200)),
('votes', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('djangobb_forum', ['PollChoice'])
# Adding model 'Poll'
db.create_table('djangobb_forum_poll', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('topic', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['djangobb_forum.Topic'])),
('question', self.gf('django.db.models.fields.CharField')(max_length=200)),
('choice_count', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=1)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('deactivate_date', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
))
db.send_create_signal('djangobb_forum', ['Poll'])
# Adding M2M table for field users on 'Poll'
db.create_table('djangobb_forum_poll_users', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('poll', models.ForeignKey(orm['djangobb_forum.poll'], null=False)),
('user', models.ForeignKey(orm['auth.user'], null=False))
))
db.create_unique('djangobb_forum_poll_users', ['poll_id', 'user_id'])
def backwards(self, orm):
# Deleting model 'PollChoice'
db.delete_table('djangobb_forum_pollchoice')
# Deleting model 'Poll'
db.delete_table('djangobb_forum_poll')
# Removing M2M table for field users on 'Poll'
db.delete_table('djangobb_forum_poll_users')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", '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'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'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', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", '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'})
},
'djangobb_forum.attachment': {
'Meta': {'object_name': 'Attachment'},
'content_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'hash': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '40', 'db_index': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attachments'", 'to': "orm['djangobb_forum.Post']"}),
'size': ('django.db.models.fields.IntegerField', [], {})
},
'djangobb_forum.ban': {
'Meta': {'object_name': 'Ban'},
'ban_end': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'ban_start': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'reason': ('django.db.models.fields.TextField', [], {}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'ban_users'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'djangobb_forum.category': {
'Meta': {'ordering': "['position']", 'object_name': 'Category'},
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'position': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'})
},
'djangobb_forum.forum': {
'Meta': {'ordering': "['position']", 'object_name': 'Forum'},
'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'forums'", 'to': "orm['djangobb_forum.Category']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_forum_post'", 'null': 'True', 'to': "orm['djangobb_forum.Post']"}),
'moderators': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'position': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'post_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'topic_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
},
'djangobb_forum.poll': {
'Meta': {'object_name': 'Poll'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'choice_count': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}),
'deactivate_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'question': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'topic': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangobb_forum.Topic']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
'djangobb_forum.pollchoice': {
'Meta': {'object_name': 'PollChoice'},
'choice': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'poll': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'choices'", 'to': "orm['djangobb_forum.Poll']"}),
'votes': ('django.db.models.fields.IntegerField', [], {'default': '0'})
},
'djangobb_forum.post': {
'Meta': {'ordering': "['created']", 'object_name': 'Post'},
'body': ('django.db.models.fields.TextField', [], {}),
'body_html': ('django.db.models.fields.TextField', [], {}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'markup': ('django.db.models.fields.CharField', [], {'default': "'bbcode'", 'max_length': '15'}),
'topic': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['djangobb_forum.Topic']"}),
'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'updated_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['auth.User']"}),
'user_ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'})
},
'djangobb_forum.posttracking': {
'Meta': {'object_name': 'PostTracking'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_read': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'topics': ('djangobb_forum.fields.JSONField', [], {'null': 'True'}),
'user': ('djangobb_forum.fields.AutoOneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'djangobb_forum.profile': {
'Meta': {'object_name': 'Profile'},
'aim': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
'auto_subscribe': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'avatar': ('djangobb_forum.fields.ExtendedImageField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'icq': ('django.db.models.fields.CharField', [], {'max_length': '12', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'jabber': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '5'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'markup': ('django.db.models.fields.CharField', [], {'default': "'bbcode'", 'max_length': '15'}),
'msn': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
'post_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'privacy_permission': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'show_avatar': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'show_signatures': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'show_smilies': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'signature': ('django.db.models.fields.TextField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'signature_html': ('django.db.models.fields.TextField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'site': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'theme': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '80'}),
'time_zone': ('django.db.models.fields.FloatField', [], {'default': '3.0'}),
'user': ('djangobb_forum.fields.AutoOneToOneField', [], {'related_name': "'forum_profile'", 'unique': 'True', 'to': "orm['auth.User']"}),
'yahoo': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'})
},
'djangobb_forum.report': {
'Meta': {'object_name': 'Report'},
'created': ('django.db.models.fields.DateTimeField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangobb_forum.Post']"}),
'reason': ('django.db.models.fields.TextField', [], {'default': "''", 'max_length': "'1000'", 'blank': 'True'}),
'reported_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reported_by'", 'to': "orm['auth.User']"}),
'zapped': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'zapped_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'zapped_by'", 'null': 'True', 'to': "orm['auth.User']"})
},
'djangobb_forum.reputation': {
'Meta': {'unique_together': "(('from_user', 'post'),)", 'object_name': 'Reputation'},
'from_user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputations_from'", 'to': "orm['auth.User']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post'", 'to': "orm['djangobb_forum.Post']"}),
'reason': ('django.db.models.fields.TextField', [], {'max_length': '1000'}),
'sign': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'to_user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reputations_to'", 'to': "orm['auth.User']"})
},
'djangobb_forum.topic': {
'Meta': {'ordering': "['-updated']", 'object_name': 'Topic'},
'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'forum': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'topics'", 'to': "orm['djangobb_forum.Forum']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_topic_post'", 'null': 'True', 'to': "orm['djangobb_forum.Post']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'post_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
'sticky': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'subscriptions'", 'blank': 'True', 'to': "orm['auth.User']"}),
'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'views': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'})
}
}
complete_apps = ['djangobb_forum']

View file

@ -1,13 +1,15 @@
from datetime import datetime
import os
import os.path
from hashlib import sha1
# coding: utf-8
from datetime import datetime
from hashlib import sha1
import os
from django.db import models
from django.contrib.auth.models import User, Group
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User, Group
from django.db import models
from django.db.models import aggregates
from django.db.models.signals import post_save
from django.utils.translation import ugettext_lazy as _
from djangobb_forum.fields import AutoOneToOneField, ExtendedImageField, JSONField
from djangobb_forum.util import smiles, convert_text_to_html
@ -410,6 +412,54 @@ class Attachment(models.Model):
self.path)
#------------------------------------------------------------------------------
class Poll(models.Model):
topic = models.ForeignKey(Topic)
question = models.CharField(max_length=200)
choice_count = models.PositiveSmallIntegerField(default=1,
help_text=_("How many choices are allowed simultaneously."),
)
active = models.BooleanField(default=True,
help_text=_("Can users vote to this poll or just see the result?"),
)
deactivate_date = models.DateTimeField(null=True, blank=True,
help_text=_("Point of time after this poll would be automatic deactivated"),
)
users = models.ManyToManyField(User, blank=True, null=True,
help_text=_("Users who has voted this poll."),
)
def auto_deactivate(self):
if self.active and self.deactivate_date:
now = datetime.now()
if now > self.deactivate_date:
self.active = False
self.save()
def __unicode__(self):
return self.question
class PollChoice(models.Model):
poll = models.ForeignKey(Poll, related_name="choices")
choice = models.CharField(max_length=200)
votes = models.IntegerField(default=0, editable=False)
def percent(self):
if not self.votes:
return 0.0
result = PollChoice.objects.filter(poll=self.poll).aggregate(aggregates.Sum("votes"))
votes_sum = result["votes__sum"]
return float(self.votes) / votes_sum * 100
def __unicode__(self):
return self.choice
#------------------------------------------------------------------------------
from .signals import post_saved, topic_saved
post_save.connect(post_saved, sender=Post, dispatch_uid='djangobb_post_save')

View file

@ -73,4 +73,24 @@ $(document).ready(function() {
paste("[b]"+nick+"[/b]\n");
});
$(".username").attr('title', 'Click to paste nick name in reply form.');
window.onbeforeunload = function() {
var obj = $("textarea#id_body");
if (obj.length != 1) {
// object not found in page -> do nothing
return
}
var text = obj.val().trim();
//log("onbeforeunload text:" + text);
if (text.length > 3) {
// Firefox will not use the string. IE use it
// TODO: Translate string
return "Leave page with unsaved content?";
}
// if nothing returned, browser leave the page without any message
};
$("form#post").bind("submit", function() {
//log("unbind onbeforeunload");
window.onbeforeunload = null;
});
});

View file

@ -315,3 +315,13 @@ TD DIV.tclcon {MARGIN-LEFT: 2.3em}
color: #8A1F11;
}
/****************************************************************/
/* for poll results */
/****************************************************************/
#poll .bar {
height: 4px;
background-color: #46586A;
}
#poll li {
height: 2.5em;
}

View file

@ -1,46 +0,0 @@
{% extends 'djangobb_forum/base.html' %}
{% load forum_extras %}
{% load i18n %}
{% block content %}
<div class="linkst">
<div class="inbox">
{% if forum %}
<ul class="start"><li><a href="{% url djangobb:index %}">{% trans "Root" %}</a> </li><li>&raquo; {% link forum %}</li></ul>
{% else %}
<ul><li><a href="{% url djangobb:index %}">{% trans "Root" %}</a> </li><li>&raquo; {% link topic.forum %}</li><li>&raquo; {{ topic }}</li></ul>
{% endif %}
<div class="clearer"></div>
</div>
</div>
{% include "djangobb_forum/includes/post_form.html" %}
{% if not forum %}
<div id="postreview" class="blockpost">
<h2><span>{% trans "Topic review (newest first)" %}</span></h2>
{% for post in posts reversed %}
<div class="box rowodd">
<div class="inbox">
<div class="postleft">
<dl>
<dt><strong><a href="javascript:pasteN('{{ post.user.username }}');">{{ post.user.username }}</a></strong></dt>
<dd>{% forum_time post.created %}</dd>
</dl>
</div>
<div class="postright">
<div class="postmsg">
{{ post.body_html|safe }}
</div>
</div>
<div class="clearer"></div>
<div class="postfootright"><ul><li class="postquote"><a onmouseover="copyQ('{{ post.user.username }}');" href="javascript:pasteQ();">{% trans "Quote" %}</a></li></ul></div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,17 @@
{% extends 'djangobb_forum/base.html' %}
{% load forum_extras %}
{% load i18n %}
{% block content %}
<div class="linkst">
<div class="inbox">
{% if forum %}
<ul class="start"><li><a href="{% url djangobb:index %}">{% trans "Root" %}</a> </li><li>&raquo; {% link forum %}</li></ul>
{% else %}
<ul><li><a href="{% url djangobb:index %}">{% trans "Root" %}</a> </li><li>&raquo; {% link topic.forum %}</li><li>&raquo; {{ topic }}</li></ul>
{% endif %}
<div class="clearer"></div>
</div>
</div>
{% include "djangobb_forum/includes/post_form.html" %}
{% endblock %}

View file

@ -2,8 +2,30 @@
<div class="blockform" id="reply">
<h2><span>{% if forum %}{% trans "New topic" %}{% else %}{% trans "New reply" %}{% endif %}</span></h2>
<div class="box">
<form id="post" action="{% if forum %}{% url djangobb:add_topic forum.id %}{% else %}{% url djangobb:add_post topic.id %}{% endif %}" method="post" enctype="multipart/form-data">
<form id="post" action="{{ form_url|default_if_none:"." }}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% if create_poll_form %}
<script>{# TODO: move to html head! #}
$(document).ready(function() {
$('.poll .infldset').hide();
$(".poll").click(function() {
$('.poll .infldset').slideDown();
});
});
</script>
<div class="inform poll">
<fieldset>
<legend>{% trans "Create a poll" %}</legend>
<div class="infldset">
<div class="rbox">
{{ create_poll_form }}
</div>
</div>
</fieldset>
</div>
{% endif %}
<div class="inform">
<fieldset>
<legend>{% trans "Write your message and submit" %}</legend>
@ -33,7 +55,7 @@
</fieldset>
</div>
{% endif %}
<p><input type="submit" value="{% trans "Submit" %}" /><a href="javascript:history.go(-1)">{% trans "Go back" %}</a></p>
<p><input type="submit" name="{{ form.FORM_NAME }}" value="{% trans "Submit" %}" /><a href="{{ back_url|default_if_none:"javascript:history.go(-1)" }}">{% trans "Go back" %}</a></p>
</form>
</div>
</div>

View file

@ -18,6 +18,37 @@
<div class="clearer"></div>
</div>
</div>
{% if poll %}
<div id="poll" class="block">
<h2><span>{% trans "Poll" %}</span></h2>
<div class="box">
<div class="inbox"><p><strong>{{ poll.question }}</strong></p>
{% if poll_form %}
<form action="." method="post">{% csrf_token %}
{{ poll_form }}
{% if poll.choice_count > 1 %}
<p>
{% blocktrans with count=poll.choice_count %}({{ count }} answers allows.){% endblocktrans %}
</p>
{% endif %}
<input type="submit" name="{{ poll_form.FORM_NAME }}" value="{% trans "Vote" %}" />
</form>
{% else %}
<ul>
{% for choice in poll.choices.all %}
<li>
<div class="bar" style="width:{{ choice.percent }}%;" title="{{ choice.choice }}">&nbsp;</div>
{{ choice.votes }} vote{{ choice.votes|pluralize }} ({{ choice.percent|floatformat:1 }}%) for: {{ choice.choice }}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
</div>
{% endif %}
{% for post in posts %}
<div id="p{{ post.id }}" class="blockpost roweven firstpost">
<a name="post-{{ post.id }}"></a>
@ -152,10 +183,14 @@
<div class="clearer"></div>
</div>
</div>
{% if not topic.closed and user.is_authenticated %}
{% if reply_form %}
{% with form=reply_form %}
{% include "djangobb_forum/includes/post_form.html" %}
{% endwith %}
{% endif %}
{% endblock %}
{% endblock content%}
{% block controls %}
<div class="conl">

View file

@ -2,10 +2,10 @@ from django.conf.urls.defaults import *
from djangobb_forum import settings as forum_settings
from djangobb_forum import views as forum_views
from djangobb_forum.feeds import LastPosts, LastTopics, LastPostsOnForum,\
from djangobb_forum.feeds import LastPosts, LastTopics, LastPostsOnForum, \
LastPostsOnCategory, LastPostsOnTopic
from djangobb_forum.forms import EssentialsProfileForm,\
PersonalProfileForm, MessagingProfileForm, PersonalityProfileForm,\
from djangobb_forum.forms import EssentialsProfileForm, \
PersonalProfileForm, MessagingProfileForm, PersonalityProfileForm, \
DisplayProfileForm, PrivacyProfileForm, UploadAvatarForm
@ -54,16 +54,13 @@ urlpatterns = patterns('',
# Topic
url('^topic/(?P<topic_id>\d+)/$', forum_views.show_topic, name='topic'),
url('^(?P<forum_id>\d+)/topic/add/$', forum_views.add_post,
{'topic_id': None}, name='add_topic'),
url('^(?P<forum_id>\d+)/topic/add/$', forum_views.add_topic, name='add_topic'),
url('^topic/(?P<topic_id>\d+)/delete_posts/$', forum_views.delete_posts, name='delete_posts'),
url('^topic/move/$', forum_views.move_topic, name='move_topic'),
url('^topic/(?P<topic_id>\d+)/stick_unstick/(?P<action>[s|u])/$', forum_views.stick_unstick_topic, name='stick_unstick_topic'),
url('^topic/(?P<topic_id>\d+)/open_close/(?P<action>[c|o])/$', forum_views.open_close_topic, name='open_close_topic'),
# Post
url('^topic/(?P<topic_id>\d+)/post/add/$', forum_views.add_post,
{'forum_id': None}, name='add_post'),
url('^post/(?P<post_id>\d+)/$', forum_views.show_post, name='post'),
url('^post/(?P<post_id>\d+)/edit/$', forum_views.edit_post, name='edit_post'),
url('^post/(?P<post_id>\d+)/delete/$', forum_views.delete_post, name='delete_post'),

View file

@ -1,33 +1,35 @@
# coding: utf-8
import math
from datetime import datetime, timedelta
from django.shortcuts import get_object_or_404, render
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseForbidden
from django.contrib.auth.models import User
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
from django.core.cache import cache
from django.db.models import Q, F, Sum
from django.utils.encoding import smart_str
from django.core.urlresolvers import reverse
from django.db import transaction
from django.views.decorators.csrf import csrf_exempt
from django.db.models import Q, F
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import get_object_or_404, render
from django.utils.encoding import smart_str
from django.utils.translation import ugettext as _
from django.views.decorators.csrf import csrf_exempt
from haystack.query import SearchQuerySet, SQ
from djangobb_forum import settings as forum_settings
from djangobb_forum.forms import AddPostForm, EditPostForm, UserSearchForm, \
PostSearchForm, ReputationForm, MailToForm, EssentialsProfileForm, \
PersonalProfileForm, MessagingProfileForm, PersonalityProfileForm, \
DisplayProfileForm, PrivacyProfileForm, ReportForm, UploadAvatarForm
from djangobb_forum.models import Category, Forum, Topic, Post, Profile, Reputation, \
VotePollForm, ReportForm, VotePollForm, PollForm
from djangobb_forum.models import Category, Forum, Topic, Post, Reputation, \
Attachment, PostTracking
from djangobb_forum.templatetags import forum_extras
from djangobb_forum.templatetags.forum_extras import forum_moderated_by
from djangobb_forum.util import build_form, paginate, set_language, smiles, convert_text_to_html
from haystack.query import SearchQuerySet, SQ
from django.contrib import messages
from django.core.exceptions import SuspiciousOperation
def index(request, full=True):
@ -312,6 +314,19 @@ def show_forum(request, forum_id, full=True):
@transaction.commit_on_success
def show_topic(request, topic_id, full=True):
"""
* Display a topic
* save a reply
* save a poll vote
TODO: Add reply in lofi mode
"""
post_request = request.method == "POST"
user_is_authenticated = request.user.is_authenticated()
if post_request and not user_is_authenticated:
# Info: only user that are logged in should get forms in the page.
return HttpResponseForbidden()
topic = get_object_or_404(Topic.objects.select_related(), pk=topic_id)
if not topic.forum.category.has_access(request.user):
return HttpResponseForbidden()
@ -323,82 +338,147 @@ def show_topic(request, topic_id, full=True):
topic.update_read(request.user)
posts = topic.posts.all().select_related()
initial = {}
if request.user.is_authenticated():
initial = {
'markup': request.user.forum_profile.markup,
'subscribe': request.user.forum_profile.auto_subscribe,
}
form = AddPostForm(topic=topic, initial=initial)
moderator = request.user.is_superuser or\
request.user in topic.forum.moderators.all()
if request.user.is_authenticated() and request.user in topic.subscribers.all():
moderator = request.user.is_superuser or request.user in topic.forum.moderators.all()
if user_is_authenticated and request.user in topic.subscribers.all():
subscribed = True
else:
subscribed = False
# reply form
reply_form = None
form_url = None
back_url = None
if user_is_authenticated and not topic.closed:
form_url = request.path + "#reply" # if form validation failed: browser should scroll down to reply form ;)
back_url = request.path
ip = request.META.get('REMOTE_ADDR', None)
post_form_kwargs = {"topic":topic, "user":request.user, "ip":ip}
if post_request and AddPostForm.FORM_NAME in request.POST:
reply_form = AddPostForm(request.POST, request.FILES, **post_form_kwargs)
if reply_form.is_valid():
post = reply_form.save()
messages.success(request, _("Your reply saved."))
return HttpResponseRedirect(post.get_absolute_url())
else:
reply_form = AddPostForm(
initial={
'markup': request.user.forum_profile.markup,
'subscribe': request.user.forum_profile.auto_subscribe,
},
**post_form_kwargs
)
# handle poll, if exists
poll_form = None
polls = topic.poll_set.all()
if not polls:
poll = None
else:
poll = polls[0]
if user_is_authenticated: # Only logged in users can vote
poll.auto_deactivate()
has_voted = request.user in poll.users.all()
if not post_request or not VotePollForm.FORM_NAME in request.POST:
# It's not a POST request or: The reply form was send and not a poll vote
if poll.active and not has_voted:
poll_form = VotePollForm(poll)
else:
if not poll.active:
messages.error(request, _("This poll is not active!"))
return HttpResponseRedirect(topic.get_absolute_url())
elif has_voted:
messages.error(request, _("You have already vote to this poll in the past!"))
return HttpResponseRedirect(topic.get_absolute_url())
poll_form = VotePollForm(poll, request.POST)
if poll_form.is_valid():
ids = poll_form.cleaned_data["choice"]
queryset = poll.choices.filter(id__in=ids)
queryset.update(votes=F('votes') + 1)
poll.users.add(request.user) # save that this user has vote
messages.success(request, _("Your votes are saved."))
return HttpResponseRedirect(topic.get_absolute_url())
highlight_word = request.GET.get('hl', '')
if full:
return render(request, 'djangobb_forum/topic.html', {'categories': Category.objects.all(),
'topic': topic,
'last_post': last_post,
'form': form,
'form_url': form_url,
'reply_form': reply_form,
'back_url': back_url,
'moderator': moderator,
'subscribed': subscribed,
'posts': posts,
'highlight_word': highlight_word,
'poll': poll,
'poll_form': poll_form,
})
else:
return render(request, 'djangobb_forum/lofi/topic.html', {'categories': Category.objects.all(),
'topic': topic,
'posts': posts,
'poll': poll,
'poll_form': poll_form,
})
@login_required
@transaction.commit_on_success
def add_post(request, forum_id, topic_id):
forum = None
topic = None
posts = None
if forum_id:
def add_topic(request, forum_id):
"""
create a new topic, with or without poll
"""
forum = get_object_or_404(Forum, pk=forum_id)
if not forum.category.has_access(request.user):
return HttpResponseForbidden()
elif topic_id:
topic = get_object_or_404(Topic, pk=topic_id)
posts = topic.posts.all().select_related()
if not topic.forum.category.has_access(request.user):
return HttpResponseForbidden()
if topic and topic.closed:
messages.error(request, _("This topic is closed."))
return HttpResponseRedirect(topic.get_absolute_url())
ip = request.META.get('REMOTE_ADDR', None)
form = build_form(AddPostForm, request, topic=topic, forum=forum,
user=request.user, ip=ip,
post_form_kwargs = {"forum":forum, "user":request.user, "ip":ip, }
if request.method == 'POST':
form = AddPostForm(request.POST, request.FILES, **post_form_kwargs)
if form.is_valid():
all_valid = True
else:
all_valid = False
poll_form = PollForm(request.POST)
create_poll = poll_form.create_poll()
if not create_poll:
# All poll fields are empty: User didn't want to create a poll
# Don't run validation and remove all form error messages
poll_form = PollForm() # create clean form without form errors
elif not poll_form.is_valid():
all_valid = False
if all_valid:
post = form.save()
if create_poll:
poll_form.save(post)
messages.success(request, _("Topic with poll saved."))
else:
messages.success(request, _("Topic saved."))
return HttpResponseRedirect(post.get_absolute_url())
else:
form = AddPostForm(
initial={
'markup': request.user.forum_profile.markup,
'subscribe': request.user.forum_profile.auto_subscribe,
})
},
**post_form_kwargs
)
if forum_id: # Create a new topic
poll_form = PollForm()
if 'post_id' in request.GET:
post_id = request.GET['post_id']
post = get_object_or_404(Post, pk=post_id)
form.fields['body'].initial = u"[quote=%s]%s[/quote]" % (post.user, post.body)
if form.is_valid():
post = form.save();
messages.success(request, _("Topic saved."))
return HttpResponseRedirect(post.get_absolute_url())
return render(request, 'djangobb_forum/add_post.html', {'form': form,
'posts': posts,
'topic': topic,
context = {
'forum': forum,
})
'create_poll_form': poll_form,
'form': form,
'form_url': request.path,
'back_url': forum.get_absolute_url(),
}
return render(request, 'djangobb_forum/add_topic.html', context)
@transaction.commit_on_success