From de5e37d2669d5874b1f82532817e18e8e202e4c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Larchev=C3=AAque?= Date: Tue, 6 Aug 2013 11:32:05 -0400 Subject: [PATCH] model + setup form --- .gitignore | 2 + secretquestions/__init__.py | 1 + secretquestions/admin.py | 7 ++ secretquestions/forms.py | 64 ++++++++++++++ secretquestions/migrations/0001_initial.py | 87 +++++++++++++++++++ secretquestions/migrations/__init__.py | 0 secretquestions/models.py | 29 +++++++ .../templates/secretquestions/setup_form.html | 23 +++++ secretquestions/urls.py | 8 ++ secretquestions/views.py | 31 +++++++ setup.cfg | 3 + setup.py | 26 ++++++ 12 files changed, 281 insertions(+) create mode 100644 .gitignore create mode 100644 secretquestions/__init__.py create mode 100644 secretquestions/admin.py create mode 100644 secretquestions/forms.py create mode 100644 secretquestions/migrations/0001_initial.py create mode 100644 secretquestions/migrations/__init__.py create mode 100644 secretquestions/models.py create mode 100644 secretquestions/templates/secretquestions/setup_form.html create mode 100644 secretquestions/urls.py create mode 100644 secretquestions/views.py create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ab10f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*egg-info +*.pyc diff --git a/secretquestions/__init__.py b/secretquestions/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/secretquestions/__init__.py @@ -0,0 +1 @@ +# diff --git a/secretquestions/admin.py b/secretquestions/admin.py new file mode 100644 index 0000000..f1c282b --- /dev/null +++ b/secretquestions/admin.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +from django.contrib import admin + +from .models import Question + +admin.site.register(Question) diff --git a/secretquestions/forms.py b/secretquestions/forms.py new file mode 100644 index 0000000..2a13145 --- /dev/null +++ b/secretquestions/forms.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +from django.conf import settings +from django import forms +from django.forms.models import modelformset_factory, ModelForm +from django.utils.translation import ugettext as _ + +from .models import Answer, crypt_answer + + +MAX_SECRET_QUESTIONS = getattr(settings, 'MAX_SECRET_QUESTIONS', 3) + + +class AnswerForm(ModelForm): + + class Meta: + model = Answer + + def __init__(self, *args, **kwargs): + if 'instance' in kwargs: + kwargs['instance'].secret = "" + super(AnswerForm, self).__init__(*args, **kwargs) + + def clean_secret(self): + data = self.cleaned_data['secret'] + return crypt_answer(data) + + +_FreeAnswerFormSet = modelformset_factory(Answer, form=AnswerForm, + fields=("question", "secret"), + extra=MAX_SECRET_QUESTIONS, + max_num=MAX_SECRET_QUESTIONS, + can_delete=False) + + +class AnswerFormSet(_FreeAnswerFormSet): + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop('user') + super(AnswerFormSet, self).__init__(*args, **kwargs) + + def save_all(self): + instances = self.save(commit=False) + for instance in instances: + instance.user = self.user + instance.save() + + def clean(self): + questions = [] + for i in range(0, self.total_form_count()): + form = self.forms[i] + try: + question = form.cleaned_data.get('question') + except: + question = None + if question is None: + raise forms.ValidationError( + _("All questions have to be selected.")) + if question in questions: + raise forms.ValidationError( + _("Each question has to be different.")) + questions.append(question) + + return super(AnswerFormSet, self).clean() diff --git a/secretquestions/migrations/0001_initial.py b/secretquestions/migrations/0001_initial.py new file mode 100644 index 0000000..02ce945 --- /dev/null +++ b/secretquestions/migrations/0001_initial.py @@ -0,0 +1,87 @@ +# -*- 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 'Question' + db.create_table('secretquestions_question', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('text', self.gf('django.db.models.fields.CharField')(max_length=255)), + )) + db.send_create_signal('secretquestions', ['Question']) + + # Adding model 'Answer' + db.create_table('secretquestions_answer', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('question', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['secretquestions.Question'])), + ('secret', self.gf('django.db.models.fields.CharField')(max_length=255)), + )) + db.send_create_signal('secretquestions', ['Answer']) + + + def backwards(self, orm): + # Deleting model 'Question' + db.delete_table('secretquestions_question') + + # Deleting model 'Answer' + db.delete_table('secretquestions_answer') + + + 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'}) + }, + 'secretquestions.answer': { + 'Meta': {'object_name': 'Answer'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['secretquestions.Question']"}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'secretquestions.question': { + 'Meta': {'object_name': 'Question'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'text': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['secretquestions'] \ No newline at end of file diff --git a/secretquestions/migrations/__init__.py b/secretquestions/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/secretquestions/models.py b/secretquestions/models.py new file mode 100644 index 0000000..f567ed4 --- /dev/null +++ b/secretquestions/models.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from django.db import models + +from django.contrib.auth.models import get_hexdigest + + +def crypt_answer(raw): + import random + algo = 'sha1' + salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5] + hsh = get_hexdigest(algo, salt, raw) + return '%s$%s$%s' % (algo, salt, hsh) + + +class Question(models.Model): + text = models.CharField(max_length=255) + + def __unicode__(self): + return self.text + + +class Answer(models.Model): + user = models.ForeignKey('auth.User') + question = models.ForeignKey('secretquestions.Question') + secret = models.CharField(max_length=255) + + class Meta: + unique_together = ('question', 'user') diff --git a/secretquestions/templates/secretquestions/setup_form.html b/secretquestions/templates/secretquestions/setup_form.html new file mode 100644 index 0000000..c76e9d6 --- /dev/null +++ b/secretquestions/templates/secretquestions/setup_form.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} + +

+{% blocktrans %} +Select several questions and register your secret answers. +These one will be asked to you for some critical features which required more +security to ensure that is you. +{% endblocktrans %} +

+ +{{ formset.non_form_errors.as_ul }} + +
{% csrf_token %} + + {{ formset }} +
+ +
+ +{% endblock content %} diff --git a/secretquestions/urls.py b/secretquestions/urls.py new file mode 100644 index 0000000..1dbcc54 --- /dev/null +++ b/secretquestions/urls.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +from django.conf.urls.defaults import patterns + +urlpatterns = patterns('', + (r'questions/setup$', 'secretquestions.views.setup_form'), + (r'questions/ask$','secretquestions.views.ask_form'), +) diff --git a/secretquestions/views.py b/secretquestions/views.py new file mode 100644 index 0000000..386db09 --- /dev/null +++ b/secretquestions/views.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +from django.template import RequestContext +from django.shortcuts import render_to_response, redirect +from django.conf import settings +from django.utils.translation import ugettext as _ + +from django.contrib.auth.decorators import login_required +from django.contrib import messages + +from .forms import AnswerFormSet + + +@login_required +def setup_form(request): + if request.method == 'POST': + formset = AnswerFormSet(request.POST, user=request.user) + if formset.is_valid(): + formset.save_all() + messages.info(request, _("Your secret answers were successfully saved.")) + return redirect(settings.LOGIN_REDIRECT_URL) + else: + formset = AnswerFormSet(user=request.user) + return render_to_response("secretquestions/setup_form.html", { + "formset": formset, + }, + context_instance=RequestContext(request)) + + +def ask_form(request): + pass diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..01bb954 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[egg_info] +tag_build = dev +tag_svn_revision = true diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5061f71 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +from setuptools import setup, find_packages +import sys, os + +version = '0.0' + +setup(name='django-secretquestions', + version=version, + description="Provides secret questions toolkit", + long_description="""\ +""", + classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers + keywords='django secretquestions authentication security', + author='Olivier Larchev\xc3\xaaque', + author_email='olivier.larcheveque@auf.org', + url='', + license='', + packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + include_package_data=True, + zip_safe=False, + install_requires=[ + # -*- Extra requirements: -*- + ], + entry_points=""" + # -*- Entry points: -*- + """, + )