Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update the readme to correctly refer to github source rather than google repository (which is apparently abandoned) #4

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7584783
Direct people to the correct source code repository (on github) rathe…
wahuneke Jul 5, 2013
a7d13ef
Add 2 new fields to survey model. These fields allow the addition of…
wahuneke Jul 5, 2013
bc58c65
Adding the static files directly in the app.
mattions Feb 18, 2013
9d6f966
Add javascript that looks for the new 'arbitrary' field on multichoic…
wahuneke Jul 7, 2013
3f8a765
Add a new setting CROWDSOURCING_LOGIN_AUTOREDIRECT that will cause th…
wahuneke Jul 7, 2013
958acaf
Tidy up the admin interface
wahuneke Jul 7, 2013
020fafc
Tidy up the admin interface
wahuneke Jul 7, 2013
c408df6
Add option to limit the question types that are offered in the admin …
wahuneke Jul 7, 2013
14d4cf7
Add tastypie integration to add a few basic API abilities.
wahuneke Jul 7, 2013
c82a339
Ability to incorporate data from multiple surveys into one survey rep…
wahuneke Jul 7, 2013
508f255
activate the jquery tagit plugin to help figure out the field names f…
wahuneke Jul 7, 2013
baca98d
Finish integration of tagit plugin for use in admin when setting up s…
wahuneke Jul 7, 2013
a79e937
tagit plugin should use space and not comma
wahuneke Jul 7, 2013
2da4f71
form validation in survey report to make sure that the provided field…
wahuneke Jul 7, 2013
21d4f8c
Enable use of custom usermodel. refer to the AUTH_USER_MODEL setting …
wahuneke Jul 8, 2013
41adceb
does not even 'compile' need to import django generic views module
wahuneke Jul 8, 2013
6c1b980
Fixes far enough to get the pie charts working for single field simpl…
wahuneke Jul 11, 2013
ab8f048
these options dont apply to pie charts, so dont mention "pie" in the …
wahuneke Jul 12, 2013
c5b950f
database literal 'true' is not compatible with sqlite. change to usi…
wahuneke Jul 12, 2013
a8f7a50
move the submission form to be the last spot, not the first (makes so…
wahuneke Jul 14, 2013
c932a27
First version that allows surveys to be resubmitted with the response…
wahuneke Jul 14, 2013
c25b147
cleanup the resubmit option a little. make it part of the survey def…
wahuneke Jul 14, 2013
b7bd840
make it look a little nicer
wahuneke Jul 15, 2013
261c4f2
Make survey forms able to properly populate from a pre-existing submi…
wahuneke Jul 15, 2013
b68fbfe
When modifying an existing survey response, make sure that all answer…
wahuneke Jul 15, 2013
135ce51
Add a dedicated view for survey completion (without a survey report) …
wahuneke Jul 17, 2013
90cbe32
rewrite the implementation of 2 axis aggregation function to allow fo…
wahuneke Jul 22, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Other Resources
~~~~~~~~~~~~~~~

* Python Cheeseshop page: http://pypi.python.org/pypi/django-crowdsourcing
* Source code repository and ticket tracker: http://code.google.com/p/django-crowdsourcing/
* NEW Source code repository and ticket tracker (as of 1.1.35): https://github.com/wnyc/django-crowdsourcing
* OLD Source code repository and ticket tracker: http://code.google.com/p/django-crowdsourcing/
* Discussion group: http://groups.google.com/group/django-crowdsourcing/

121 changes: 108 additions & 13 deletions crowdsourcing/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
from django.forms import ModelForm, ValidationError
from django.forms.widgets import Select
from django.utils.translation import ugettext_lazy as _
from crowdsourcing.util import remove_by_lambda

from .models import (Question, Survey, Answer, Submission,
SurveyReport, SurveyReportDisplay, OPTION_TYPE_CHOICES,
SURVEY_DISPLAY_TYPE_CHOICES,
SURVEY_AGGREGATE_TYPE_CHOICES, FORMAT_CHOICES)

from .settings import *

try:
from .flickrsupport import get_group_names, get_group_id
Expand Down Expand Up @@ -56,6 +57,20 @@ class QuestionInline(admin.StackedInline):
model = Question
extra = 3
form = QuestionForm
fields = [
('question','fieldname',),
('label','help_text',),
('required','order',),
('option_type','options',),
('allow_arbitrary','arbitrary_label'),
'map_icons',
('answer_is_public', 'use_as_filter',),
]

def __init__(self, parent_model, admin_site):
super(QuestionInline, self).__init__(parent_model, admin_site)
if MAPS_HIDE:
remove_by_lambda(self.fields, lambda x: x=='map_icons')


def _flickr_group_choices():
Expand All @@ -69,12 +84,15 @@ class SurveyAdminForm(ModelForm):
def __init__(self, *args, **kwargs):
super(SurveyAdminForm, self).__init__(*args, **kwargs)
qs = SurveyReport.objects.filter(survey=self.instance)
self.fields['default_report'].queryset = qs
self.fields['flickr_group_name'].widget = Select(choices=_flickr_group_choices())
if 'default_report' in self.fields:
self.fields['default_report'].queryset = qs
if 'flickr_group_name' in self.fields:
self.fields['flickr_group_name'].widget = Select(choices=_flickr_group_choices())

class Meta:
model = Survey


def clean_flickr_group_name(self):
group = self.cleaned_data.get('flickr_group_name', "")
if group:
Expand Down Expand Up @@ -120,7 +138,23 @@ class SurveyAdmin(admin.ModelAdmin):
list_filter = ('survey_date', 'is_published', 'site')
date_hierarchy = 'survey_date'
inlines = [QuestionInline]

fields = [
('title', 'slug','site','default_report'),
('tease','description','thanks',),
('require_login','allow_multiple_submissions','moderate_submissions','allow_comments','allow_voting',),
('starts_at','ends_at',),
('archive_policy','is_published',),
'email',
'flickr_group_name',
]
class Media:
js = ("crowdsourcing/admin.js",)
css = {'all': ("crowdsourcing/admin.css",),
}
def __init__(self, parent_model, admin_site):
super(SurveyAdmin, self).__init__(parent_model, admin_site)
if FLICKR_HIDE:
remove_by_lambda(self.fields, lambda x: x=='flickr_group_name')

admin.site.register(Survey, SurveyAdmin)

Expand Down Expand Up @@ -155,6 +189,10 @@ class SubmissionAdmin(admin.ModelAdmin):


class SurveyReportDisplayInlineForm(ModelForm):
def _is_valid_fieldname(self, fieldname):
report = self.cleaned_data.get("report")
return not report.find_question(fieldname) is None

def clean(self):
display_type = self.cleaned_data.get("display_type", "")
aggregate_type = self.cleaned_data.get("aggregate_type", "")
Expand Down Expand Up @@ -186,6 +224,18 @@ def clean(self):
elif display_type == PIE and not is_count:
raise ValidationError(_(
"Use 'Default' or 'Count' for Pie charts."))

# CANT do this. works great most of the time but does not work if you are modifying
# an existing report and you change the list of surveys it's based on and then go
# and change the fielnames... perhaps this display model still points to a report
# object that is not yet updated to have the correct set of surveys in it.
#
# if fieldnames != "":
# fieldnames = fieldnames.split(" ")
# invalid_fields = [f for f in fieldnames if not self._is_valid_fieldname(f)]
# if invalid_fields:
# raise ValidationError(_("You have invalid field(s) in your fieldnames list"))

return self.cleaned_data

class Meta:
Expand All @@ -195,34 +245,79 @@ class Meta:
class SurveyReportDisplayInline(admin.StackedInline):
form = SurveyReportDisplayInlineForm

fieldsets = (
fieldsets = [
(None,
{'fields': (
'display_type',
'fieldnames',
('display_type', 'fieldnames',),
'annotation',
'order',)}),
('Pie, Line, and Bar Charts',
'order',),
#'classes': ('collapse',),
}),
('Line, and Bar Charts',
{'fields': (
'aggregate_type',
'x_axis_fieldname',)}),
'x_axis_fieldname',),
'classes': ('collapse',),
}),
('Slideshow',
{'fields': ('caption_fields',)}),
{'fields': ('caption_fields',),
'classes': ('collapse',),
}),
('Maps',
{'fields': (
'limit_map_answers',
'map_center_latitude',
'map_center_longitude',
'map_zoom',)}))
'map_zoom',),
'classes': ('collapse',),
})]

model = SurveyReportDisplay
extra = 3

def __init__(self, parent_model, admin_site):
super(SurveyReportDisplayInline, self).__init__(parent_model, admin_site)
def find(l,func):
"""
return the index of the first item in list (l) for which
the function (func) is True
return -1 if not found
"""
for i,item in enumerate(l):
if func(item):
return i
return -1

if MAPS_HIDE:
remove_by_lambda(self.fieldsets, lambda x: x[0]=="Maps")

if FLICKR_HIDE:
remove_by_lambda(self.fieldsets, lambda x: x[0]=="Slideshow")


class SurveyReportAdmin(admin.ModelAdmin):
list_display = ('__unicode__', 'slug', 'survey',)
list_display = ('__unicode__', 'slug',)
prepopulated_fields = {'slug': ('title',)}
inlines = [SurveyReportDisplayInline]
fields = (
('survey','title','slug',),
'summary',
('sort_by_rating','display_the_filters','limit_results_to','featured','display_individual_results',),
)
class Media:
js = (
"crowdsourcing/jquery-1.10.1.min.js",
"crowdsourcing/jquery-ui/jquery-ui-1.10.3.js",
"crowdsourcing/jquery.tagit/js/tag-it.min.js",
"crowdsourcing/admin.js",
)
css = {
'all': (
"crowdsourcing/admin.css",
"crowdsourcing/jquery.tagit/css/jquery.tagit.css",
"crowdsourcing/jquery-ui/jquery-ui.min.css",
),
}


admin.site.register(SurveyReport, SurveyReportAdmin)
88 changes: 77 additions & 11 deletions crowdsourcing/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Select,
Textarea,
ValidationError,
TextInput,
)
from django.forms.forms import BoundField
from django.forms.formsets import BaseFormSet
Expand Down Expand Up @@ -52,6 +53,17 @@ def __init__(self,
self.submission = submission
super(BaseAnswerForm, self).__init__(*args, **kwargs)
self._configure_answer_field()
self.populate_from_submission()


def populate_from_submission(self):
if self.submission is None:
return
answers = self.submission.get_question_answers(self.question)
# override this for answer types that can have multiple answer records
# per submission per question
if len(answers) == 1:
self.fields['answer'].initial = answers[0].value

def _configure_answer_field(self):
answer = self.fields['answer']
Expand All @@ -67,14 +79,19 @@ def as_template(self):
bound_fields = [BoundField(self, field, name) \
for name, field in self.fields.items()]
c = Context(dict(form=self, bound_fields=bound_fields))
t = loader.get_template('forms/form.html')
t = loader.get_template('crowdsourcing/forms/form.html')
return t.render(c)

def save(self, commit=True):
if self.cleaned_data['answer'] is None:
if self.fields['answer'].required:
raise ValidationError, _('This field is required.')
return
# dont wind up recording answers multiple times for the same question
# in the same submission (which can happen for surveys where it is enabled
# to resubmit the survey (change your answers)
if self.submission:
self.submission.get_question_answers(self.question).delete()
ans = Answer()
if self.submission:
ans.submission = self.submission
Expand Down Expand Up @@ -175,25 +192,60 @@ def __init__(self, *args, **kwargs):
options = self.question.parsed_options
# appendChoiceButtons in survey.js duplicates this. jQuery and django
# use " for html attributes, so " will mess them up.
choices = []
for x in options:
choices.append(
(strip_tags(x).replace('&', '&').replace('"', "'").strip(),
mark_safe(x)))
choices = [self.make_choice(x) for x in options]
if not self.question.required and not isinstance(self, OptionCheckbox):
choices = [('', '---------',)] + choices
if self.question.allow_arbitrary:
choices.append(('arbitrary_answer',self.question.arbitrary_label))
self.fields['answer'].choices = choices
if self.question.allow_arbitrary:
choice_control_name = self.add_prefix('answer')
widget = TextInput(attrs={'arb_boundto':choice_control_name,'arb_choice':'arbitrary_answer','class':'arbitrary_textbox'})
self.fields['answer_arbitrary'] = CharField(label="", widget=widget, required=False)
self.populate_from_submission()

def make_choice(self, str):
"""
Convert given option string into a key,value tuple suitable for use as option value
"""
key = strip_tags(str).replace('&', '&').replace('"', "'").strip()
val = mark_safe(str)
return key, val

def populate_from_submission(self):
if self.submission is None:
return
answers = self.submission.get_question_answers(self.question)
if len(answers) == 1 and 'answer_arbitrary' in self.fields:
val = answers[0].value
# If val is not one of our standard options, then it must mean the
# user has chosen the 'arbitrary option'
if not any([x[1] == val for x in self.fields['answer'].choices]):
self.fields['answer'].initial = 'arbitrary_answer'
self.fields['answer_arbitrary'].initial = val
else:
self.fields['answer'].initial,dummy = self.make_choice(val)
elif len(answers) == 1:
self.fields['answer'].initial,dummy = self.make_choice(answers[0].value)

def clean_answer(self):
key = self.cleaned_data['answer']
if not key and self.fields['answer'].required:
raise ValidationError, _('This field is required.')
if not isinstance(key, (list, tuple)):
key = (key,)
key = [key,]
if self.question.allow_arbitrary and 'arbitrary_answer' in key:
where = key.index('arbitrary_answer')
key[where] = self._raw_value('answer_arbitrary')
return key

def save(self, commit=True):
ans_list = []
# dont wind up recording answers multiple times for the same question
# in the same submission (which can happen for surveys where it is enabled
# to resubmit the survey (change your answers)
if self.submission:
self.submission.get_question_answers(self.question).delete()
for text in self.cleaned_data['answer']:
ans = Answer()
if self.submission:
Expand All @@ -217,6 +269,18 @@ class OptionRadio(BaseOptionAnswer):
class OptionCheckbox(BaseOptionAnswer):
answer = MultipleChoiceField(widget=CheckboxSelectMultiple)

def populate_from_submission(self):
if self.submission is None:
return
answers = self.submission.get_question_answers(self.question)
self.fields['answer'].initial = [self.make_choice(x.value)[0] for x in answers]
if 'answer_arbitrary' in self.fields:
# Is there an answer that is not among the pre-written choices?
# if so, put that one in the arbitrary field
arbitrary_val = [x.value for x in answers if not x.value in self.question.parsed_options]
if len(arbitrary_val) > 0:
self.fields['answer_arbitrary'].initial = arbitrary_val[0]
self.fields['answer'].initial.append('arbitrary_answer')

# Each question gets a form with one element determined by the type for the
# answer.
Expand Down Expand Up @@ -262,10 +326,12 @@ def forms_for_survey(survey, request='testing', submission=None):
session_key = get_session(request).session_key.lower()
post = None if testing else request.POST or None
files = None if testing else request.FILES or None
main_form = SubmissionForm(survey, data=post, files=files)
return [main_form] + [
_form_for_question(q, session_key, submission, post, files)
for q in survey.questions.all().order_by("order")]
if submission:
main_form = SubmissionForm(survey, data=post, files=files, instance=submission)
else:
main_form = SubmissionForm(survey, data=post, files=files)
return [_form_for_question(q, session_key, submission, post, files)
for q in survey.questions.all().order_by("order")] + [main_form]


def _form_for_question(question,
Expand Down
Loading