First go at a user system #240 #255 #256
authorCharles Connell <charles@connells.org>
Thu, 9 Jan 2014 21:04:51 +0000 (16:04 -0500)
committerCharles Connell <charles@connells.org>
Thu, 9 Jan 2014 21:16:02 +0000 (16:16 -0500)
70 files changed:
karmaworld/apps/document_upload/models.py
karmaworld/apps/document_upload/tasks.py
karmaworld/apps/document_upload/tests.py
karmaworld/apps/document_upload/views.py
karmaworld/apps/moderation/admin.py
karmaworld/apps/notes/gdrive.py
karmaworld/apps/notes/migrations/0012_auto__chg_field_note_user.py [new file with mode: 0644]
karmaworld/apps/notes/models.py
karmaworld/apps/notes/search.py
karmaworld/apps/notes/views.py
karmaworld/apps/users/admin.py
karmaworld/apps/users/migrations/0002_auto__del_karmauser__add_userprofile.py [new file with mode: 0644]
karmaworld/apps/users/models.py
karmaworld/apps/users/views.py [new file with mode: 0644]
karmaworld/assets/css/accounts.css [new file with mode: 0644]
karmaworld/assets/css/global.css
karmaworld/assets/img/facebook.png [new file with mode: 0644]
karmaworld/settings/common.py
karmaworld/templates/account/account_inactive.html [new file with mode: 0644]
karmaworld/templates/account/base.html [new file with mode: 0644]
karmaworld/templates/account/email.html [new file with mode: 0644]
karmaworld/templates/account/email/email_confirmation_message.txt [new file with mode: 0644]
karmaworld/templates/account/email/email_confirmation_signup_message.txt [new file with mode: 0644]
karmaworld/templates/account/email/email_confirmation_signup_subject.txt [new file with mode: 0644]
karmaworld/templates/account/email/email_confirmation_subject.txt [new file with mode: 0644]
karmaworld/templates/account/email/password_reset_key_message.txt [new file with mode: 0644]
karmaworld/templates/account/email/password_reset_key_subject.txt [new file with mode: 0644]
karmaworld/templates/account/email_confirm.html [new file with mode: 0644]
karmaworld/templates/account/email_confirmed.html [new file with mode: 0644]
karmaworld/templates/account/login.html [new file with mode: 0644]
karmaworld/templates/account/logout.html [new file with mode: 0644]
karmaworld/templates/account/messages/cannot_delete_primary_email.txt [new file with mode: 0644]
karmaworld/templates/account/messages/email_confirmation_sent.txt [new file with mode: 0644]
karmaworld/templates/account/messages/email_confirmed.txt [new file with mode: 0644]
karmaworld/templates/account/messages/email_deleted.txt [new file with mode: 0644]
karmaworld/templates/account/messages/logged_in.txt [new file with mode: 0644]
karmaworld/templates/account/messages/logged_out.txt [new file with mode: 0644]
karmaworld/templates/account/messages/password_changed.txt [new file with mode: 0644]
karmaworld/templates/account/messages/password_set.txt [new file with mode: 0644]
karmaworld/templates/account/messages/primary_email_set.txt [new file with mode: 0644]
karmaworld/templates/account/messages/unverified_primary_email.txt [new file with mode: 0644]
karmaworld/templates/account/password_change.html [new file with mode: 0644]
karmaworld/templates/account/password_reset.html [new file with mode: 0644]
karmaworld/templates/account/password_reset_done.html [new file with mode: 0644]
karmaworld/templates/account/password_reset_from_key.html [new file with mode: 0644]
karmaworld/templates/account/password_reset_from_key_done.html [new file with mode: 0644]
karmaworld/templates/account/password_set.html [new file with mode: 0644]
karmaworld/templates/account/signup.html [new file with mode: 0644]
karmaworld/templates/account/signup_closed.html [new file with mode: 0644]
karmaworld/templates/account/snippets/already_logged_in.html [new file with mode: 0644]
karmaworld/templates/account/verification_sent.html [new file with mode: 0644]
karmaworld/templates/account/verified_email_required.html [new file with mode: 0644]
karmaworld/templates/base.html
karmaworld/templates/header.html
karmaworld/templates/notes/search_results.html
karmaworld/templates/partial/filepicker.html
karmaworld/templates/partial/trackers.html [new file with mode: 0644]
karmaworld/templates/socialaccount/authentication_error.html [new file with mode: 0644]
karmaworld/templates/socialaccount/base.html [new file with mode: 0644]
karmaworld/templates/socialaccount/connections.html [new file with mode: 0644]
karmaworld/templates/socialaccount/login_cancelled.html [new file with mode: 0644]
karmaworld/templates/socialaccount/messages/account_connected.txt [new file with mode: 0644]
karmaworld/templates/socialaccount/messages/account_connected_other.txt [new file with mode: 0644]
karmaworld/templates/socialaccount/messages/account_disconnected.txt [new file with mode: 0644]
karmaworld/templates/socialaccount/signup.html [new file with mode: 0644]
karmaworld/templates/socialaccount/snippets/login_extra.html [new file with mode: 0644]
karmaworld/templates/socialaccount/snippets/provider_list.html [new file with mode: 0644]
karmaworld/templates/user_profile.html [new file with mode: 0644]
karmaworld/urls.py
reqs/common.txt

index 3034ce411b6fb29b24eb55e35c07e5a86015ae1d..841b33eb955fe59568aad959537f86f2da261c45 100644 (file)
@@ -65,10 +65,9 @@ class RawDocument(Document):
             note.tags.add(tag)
         return note
 
-    def save(self, *args, **kwargs):
+    def save(self, user=None, session_key=None, *args, **kwargs):
         super(RawDocument, self).save(*args, **kwargs)
         if not self.is_processed:
-            tasks.process_raw_document.delay(self)
-
+            tasks.process_raw_document.delay(self, user, session_key)
 
 auto_add_check_unique_together(RawDocument)
index a09467dbd430a9581bf0109fc7e53b2aeee41168..bb8f6612936ce1457ffb2afba5e9f6286c53dfff 100644 (file)
@@ -10,10 +10,10 @@ from karmaworld.apps.notes.gdrive import convert_raw_document
 logger = get_task_logger(__name__)
 
 @task()
-def process_raw_document(raw_document):
+def process_raw_document(raw_document, user, session_key):
     """ Process a RawDocument instance in to a Note instance """
     try:
-        convert_raw_document(raw_document)
+        convert_raw_document(raw_document, user=user, session_key=session_key)
     except:
         logger.error(traceback.format_exc())
 
index 843aa99ce56a33304631c0fff7b4675448290adf..cf36a5eeb50634bbe65c8f70f54c151898e95953 100644 (file)
@@ -10,8 +10,13 @@ from karmaworld.apps.courses.models import Course
 from karmaworld.apps.courses.models import School
 from karmaworld.apps.document_upload.forms import RawDocumentForm
 from karmaworld.apps.notes.gdrive import *
-from karmaworld.apps.notes.models import Note
+from karmaworld.apps.notes.models import Note, find_orphan_notes
 
+TEST_USERNAME = 'alice'
+
+class _FakeRequest:
+    def __init__(self, session):
+        self.session = session
 
 class ConversionTest(TestCase):
 
@@ -22,16 +27,17 @@ class ConversionTest(TestCase):
         self.course.save()
         self.client = Client()
 
-    def doConversionForPost(self, post):
+    def doConversionForPost(self, post, user=None, session_key=None):
         self.assertEqual(Note.objects.count(), 0)
         r_d_f = RawDocumentForm(post)
         self.assertTrue(r_d_f.is_valid())
         raw_document = r_d_f.save(commit=False)
         raw_document.fp_file = post['fp_file']
-        convert_raw_document(raw_document)
+        convert_raw_document(raw_document, user=user, session_key=session_key)
         self.assertEqual(Note.objects.count(), 1)
 
     def testPlaintextConversion(self):
+        """Test upload of a plain text file"""
         self.doConversionForPost({'fp_file': 'https://www.filepicker.io/api/file/S2lhT3INSFCVFURR2RV7',
                                  'course': str(self.course.id),
                                  'name': 'graph3.txt',
@@ -39,6 +45,7 @@ class ConversionTest(TestCase):
                                  'mimetype': 'text/plain'})
 
     def testEvernoteConversion(self):
+        """Test upload of an Evernote note"""
         self.doConversionForPost({'fp_file': 'https://www.filepicker.io/api/file/vOtEo0FrSbu2WDbAOzLn',
                                  'course': str(self.course.id),
                                  'name': 'KarmaNotes test 3',
@@ -46,6 +53,7 @@ class ConversionTest(TestCase):
                                  'mimetype': 'text/enml'})
 
     def testPdfConversion(self):
+        """Test upload of a PDF"""
         self.doConversionForPost({'fp_file': 'https://www.filepicker.io/api/file/8l6mtMURnu1uXvcvJo9s',
                                  'course': str(self.course.id),
                                  'name': 'geneve_1564.pdf',
@@ -53,6 +61,7 @@ class ConversionTest(TestCase):
                                  'mimetype': 'application/pdf'})
 
     def testGarbage(self):
+        """Test upload of a file with a bogus mimetype"""
         with self.assertRaises(ValueError):
             self.doConversionForPost({'fp_file': 'https://www.filepicker.io/api/file/H85Xl8VURqiGusxhZKMl',
                                      'course': str(self.course.id),
@@ -60,4 +69,64 @@ class ConversionTest(TestCase):
                                      'tags': '',
                                      'mimetype': 'application/octet-stream'})
 
+    def testSessionUserAssociation1(self):
+        """Test setting the user of an uploaded document to a known
+        user in our database"""
+        user = User(username=TEST_USERNAME)
+        user.save()
+        self.doConversionForPost({'fp_file': 'https://www.filepicker.io/api/file/S2lhT3INSFCVFURR2RV7',
+                                 'course': str(self.course.id),
+                                 'name': 'graph3.txt',
+                                 'tags': '',
+                                 'mimetype': 'text/plain'},
+                                 user=user)
+        note = Note.objects.all()[0]
+        self.assertEqual(note.user, user)
+
+    def testSessionUserAssociation2(self):
+        """Test setting the user of an uploaded document
+        to an existing user in the database, finding them
+        through a session key."""
+        user = User(username=TEST_USERNAME)
+        user.save()
+        s = SessionStore()
+        s['_auth_user_id'] = user.pk
+        s.save()
+        self.doConversionForPost({'fp_file': 'https://www.filepicker.io/api/file/S2lhT3INSFCVFURR2RV7',
+                                 'course': str(self.course.id),
+                                 'name': 'graph3.txt',
+                                 'tags': '',
+                                 'mimetype': 'text/plain'},
+                                 session_key=s.session_key)
+        note = Note.objects.all()[0]
+        self.assertEqual(note.user, user)
+
+
+
+    def testSessionUserAssociation3(self):
+        """Test setting the user of an uploaded document
+        to an existing user in the database, finding them
+        through a session key."""
+        s = SessionStore()
+        s.save()
+        self.doConversionForPost({'fp_file': 'https://www.filepicker.io/api/file/S2lhT3INSFCVFURR2RV7',
+                                 'course': str(self.course.id),
+                                 'name': 'graph3.txt',
+                                 'tags': '',
+                                 'mimetype': 'text/plain'},
+                                 session_key=s.session_key)
+        user = User(username=TEST_USERNAME)
+        user.save()
+
+        # Normally this next bit is called automatically, but
+        # in testing we need to call it manually
+        note = Note.objects.all()[0]
+        s = SessionStore(session_key=s.session_key)
+        find_orphan_notes(note, user=user, request=_FakeRequest(s))
+
+        note = Note.objects.all()[0]
+        self.assertEqual(note.user, user)
+
+
+
 
index 58a83e2341fd01991152d6231bbf68ab75194f97..f2a38c9173335b68d0a1dd7b4e2156b5e634e752 100644 (file)
@@ -6,7 +6,6 @@ import datetime
 
 from django.http import HttpResponse
 from karmaworld.apps.document_upload.forms import RawDocumentForm
-from karmaworld.apps.users.models import KarmaUser
 
 
 def save_fp_upload(request):
@@ -16,23 +15,19 @@ def save_fp_upload(request):
     if r_d_f.is_valid():
         raw_document = r_d_f.save(commit=False)
 
-        time_a = datetime.datetime.now()
         raw_document.fp_file = request.POST['fp_file']
 
-        time_b = datetime.datetime.now()
-        delta = time_b - time_a
         raw_document.ip = request.META['REMOTE_ADDR']
         raw_document.uploaded_at = datetime.datetime.utcnow()
-        if request.POST['email'] != '':
-            raw_document.user = KarmaUser.objects.get_or_create(email=request.POST['email'])[0]
-        time_c = datetime.datetime.now()
+
         # note that .save() has the side-effect of kicking of a celery processing task
-        raw_document.save()
+        if request.user.is_authenticated():
+            raw_document.save(user=request.user)
+        else:
+            raw_document.save(session_key=request.session.session_key)
         # save the tags to the database, too. don't forget those guys.
         r_d_f.save_m2m()
-        time_d = datetime.datetime.now()
-        delta = time_d - time_c
-        print "d\t%s" % delta
+
         return HttpResponse({'success'})
     else:
         return HttpResponse(r_d_f.errors, status=400)
index 4580c0f1aa1752600c28f92aaa94be36dd25c3a4..58104cc4c8221a37541af45b744e458ba6360f4d 100644 (file)
@@ -19,7 +19,7 @@ reset_flags.short_description = "Reset flags to 0"
 class CourseModerator(CourseAdmin):
     date_heirarchy = 'updated_at'
     # Identify fields to display on the Change page
-    list_display = ('name', 'flags', 'school', 'academic_year', 'created_at', 'updated_at', 'instructor_name')
+    list_display = ('name', 'flags', 'school', 'created_at', 'updated_at', 'instructor_name')
     # Sort by highest number of flags first, and then by date for ties.
     ordering = ('-flags', '-updated_at')
     # Enable resetting flags
index 68359c9e580a646046b84b6770589a14ed55b4d7..42bc11a05457f252950708fd38d8172ea132cf4e 100644 (file)
@@ -3,6 +3,9 @@
 # Copyright (C) 2012  FinalsClub Foundation
 
 import datetime
+from django.contrib.auth.models import User
+from django.contrib.sessions.backends.db import SessionStore
+from django.core.exceptions import ObjectDoesNotExist
 import os
 import subprocess
 import tempfile
@@ -23,6 +26,7 @@ import karmaworld.secret.drive as drive
 PDF_MIMETYPE = 'application/pdf'
 PPT_MIMETYPES = ['application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation']
 
+UPLOADED_NOTES_SESSION_KEY = 'uploaded_notes'
 
 def extract_file_details(fileobj):
     details = None
@@ -172,7 +176,7 @@ def upload_to_gdrive(service, media, filename, extension=None, mimetype=None):
     return file_dict
 
 
-def convert_raw_document(raw_document):
+def convert_raw_document(raw_document, user=None, session_key=None):
     """ Upload a raw document to google drive and get a Note back"""
     fp_file = raw_document.get_file()
 
@@ -230,6 +234,33 @@ def convert_raw_document(raw_document):
     if 'year' in note_details and note_details['year']:
         note.year = note_details['year']
 
+    # If we know the user who uploaded this,
+    # associate them with the note
+    if user:
+        note.user = user
 
     # Finally, save whatever data we got back from google
     note.save()
+
+    if session_key and not user:
+        s = SessionStore(session_key=session_key)
+        # If the person who uploaded this made an
+        # account or signed in while convert_raw_document
+        # was running, associate their account with this note
+        try:
+            uid = s['_auth_user_id']
+            user = User.objects.get(pk=uid)
+            note.user = user
+            note.save()
+        # If we don't know the user who uploaded
+        # this, then we should have a session key
+        # instead. Associate this note with the session
+        # so if the uploader later creates an account,
+        # we can find notes they uploaded
+        except (KeyError, ObjectDoesNotExist):
+            uploaded_notes = s.get(UPLOADED_NOTES_SESSION_KEY, [])
+            uploaded_notes.append(note.id)
+            s[UPLOADED_NOTES_SESSION_KEY] = uploaded_notes
+            s.save()
+
+
diff --git a/karmaworld/apps/notes/migrations/0012_auto__chg_field_note_user.py b/karmaworld/apps/notes/migrations/0012_auto__chg_field_note_user.py
new file mode 100644 (file)
index 0000000..2f71f2d
--- /dev/null
@@ -0,0 +1,141 @@
+# -*- 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):
+
+        # Changing field 'Note.user'
+        db.alter_column('notes_note', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.SET_NULL))
+
+    def backwards(self, orm):
+
+        # Changing field 'Note.user'
+        db.alter_column('notes_note', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['users.KarmaUser'], null=True, on_delete=models.SET_NULL))
+
+    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'})
+        },
+        'courses.course': {
+            'Meta': {'ordering': "['-file_count', 'school', 'name']", 'unique_together': "(('school', 'name', 'instructor_name'),)", 'object_name': 'Course'},
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'department': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['courses.Department']", 'null': 'True', 'blank': 'True'}),
+            'desc': ('django.db.models.fields.TextField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'}),
+            'file_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'flags': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'instructor_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'instructor_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'school': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['courses.School']"}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}),
+            'updated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'})
+        },
+        'courses.department': {
+            'Meta': {'object_name': 'Department'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'school': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['courses.School']"}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'null': 'True', 'blank': 'True'})
+        },
+        'courses.school': {
+            'Meta': {'ordering': "['-file_count', '-priority', 'name']", 'object_name': 'School'},
+            'alias': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'facebook_id': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'file_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'hashtag': ('django.db.models.fields.CharField', [], {'max_length': '16', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'priority': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'blank': 'True'}),
+            'usde_id': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'licenses.license': {
+            'Meta': {'object_name': 'License'},
+            'html': ('django.db.models.fields.TextField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'})
+        },
+        'notes.note': {
+            'Meta': {'ordering': "['-uploaded_at']", 'object_name': 'Note'},
+            'course': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['courses.Course']"}),
+            'file_type': ('django.db.models.fields.CharField', [], {'default': "'???'", 'max_length': '15', 'null': 'True', 'blank': 'True'}),
+            'flags': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'fp_file': ('django_filepicker.models.FPFileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'gdrive_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
+            'is_hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'license': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['licenses.License']", 'null': 'True', 'blank': 'True'}),
+            'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True'}),
+            'text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'thanks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'tweeted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow', 'null': 'True'}),
+            'upstream_link': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'year': ('django.db.models.fields.IntegerField', [], {'default': '2014', 'null': 'True', 'blank': 'True'})
+        },
+        'taggit.tag': {
+            'Meta': {'ordering': "['namespace', 'name']", 'object_name': 'Tag'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'namespace': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'taggit.taggeditem': {
+            'Meta': {'object_name': 'TaggedItem'},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
+        }
+    }
+
+    complete_apps = ['notes']
\ No newline at end of file
index 57266b7b4e75602de52fae2d7839d227249fc7d4..f9f281aefe3dba074770b85a5094debc42b69297 100644 (file)
@@ -7,10 +7,15 @@
     Contains only the minimum for handling files and their representation
 """
 import datetime
+import traceback
+import logging
+from allauth.account.signals import user_logged_in
+from django.contrib.auth.models import User
 from django.core.exceptions import ObjectDoesNotExist
 from django.db.models import SET_NULL
 from django.db.models.signals import post_save, post_delete, pre_save
 from django.dispatch import receiver
+from karmaworld.apps.notes.gdrive import UPLOADED_NOTES_SESSION_KEY
 import os
 import urllib
 
@@ -29,6 +34,7 @@ from karmaworld.apps.notes.search import SearchIndex
 from karmaworld.settings.manual_unique_together import auto_add_check_unique_together
 
 
+logger = logging.getLogger(__name__)
 fs = FileSystemStorage(location=settings.MEDIA_ROOT)
 
 
@@ -58,7 +64,7 @@ class Document(models.Model):
     upstream_link   = models.URLField(max_length=1024, blank=True, null=True, unique=True)
 
     # metadata relevant to the Upload process
-    user            = models.ForeignKey('users.KarmaUser', blank=True, null=True, on_delete=SET_NULL)
+    user            = models.ForeignKey(User, blank=True, null=True, on_delete=SET_NULL)
     ip              = models.GenericIPAddressField(blank=True, null=True,
                         help_text=u"IP address of the uploader")
     uploaded_at     = models.DateTimeField(null=True, default=datetime.datetime.utcnow)
@@ -278,12 +284,15 @@ def note_save_receiver(sender, **kwargs):
         return
     note = kwargs['instance']
 
-    index = SearchIndex()
-    if kwargs['created']:
-        update_note_counts(note)
-        index.add_note(note)
-    else:
-        index.update_note(note, note.old_instance)
+    try:
+        index = SearchIndex()
+        if kwargs['created']:
+            update_note_counts(note)
+            index.add_note(note)
+        else:
+            index.update_note(note, note.old_instance)
+    except Exception:
+        logger.error("Error with IndexDen:\n" + traceback.format_exc())
 
 
 @receiver(post_delete, sender=Note, weak=False)
@@ -299,3 +308,13 @@ def note_delete_receiver(sender, **kwargs):
     # Remove document from search index
     index = SearchIndex()
     index.remove_note(note)
+
+@receiver(user_logged_in, weak=True)
+def find_orphan_notes(sender, **kwargs):
+    user = kwargs['user']
+    s = kwargs['request'].session
+    uploaded_note_ids = s.get(UPLOADED_NOTES_SESSION_KEY, [])
+    notes = Note.objects.filter(id__in=uploaded_note_ids)
+    for note in notes:
+        note.user = user
+        note.save()
index d578f5db9c5c95a54b99e1a0374e9de3f9428aa5..4a5869f3b481ba1122c39a63e8252c096b95a7a3 100644 (file)
@@ -94,7 +94,7 @@ class SearchIndex(object):
         already in the index, it will be overwritten."""
         if note.text:
             logger.info("Indexing {n}".format(n=note))
-            self.index.add_document(note.id, SearchIndex._note_to_dict(note), variables={0: note.thanks})
+            #self.index.add_document(note.id, SearchIndex._note_to_dict(note), variables={0: note.thanks})
         else:
             logger.info("Note {n} has no text, will not add to IndexDen".format(n=note))
 
@@ -114,7 +114,7 @@ class SearchIndex(object):
             new_note.course != old_note.course or \
             new_note.uploaded_at != old_note.uploaded_at:
             logger.info("Indexing {n}".format(n=new_note))
-            self.index.add_document(new_note.id, SearchIndex._note_to_dict(new_note), variables={0: new_note.thanks})
+            #self.index.add_document(new_note.id, SearchIndex._note_to_dict(new_note), variables={0: new_note.thanks})
 
         # If only the thanks count has changed, we can
         # just send that
index 4cd3e3ec18374570164ef2fe2940afa6d34346c1..322aa004e808877a10bcc4f9f378702f9756fc30 100644 (file)
@@ -3,6 +3,8 @@
 # Copyright (C) 2012  FinalsClub Foundation
 
 import json
+import traceback
+import logging
 from django.core.exceptions import ObjectDoesNotExist
 from karmaworld.apps.courses.models import Course
 from karmaworld.apps.notes.search import SearchIndex
@@ -19,6 +21,7 @@ from django.views.generic.detail import SingleObjectMixin
 from karmaworld.apps.notes.models import Note
 from karmaworld.apps.notes.forms import NoteForm
 
+logger = logging.getLogger(__name__)
 
 PDF_MIMETYPES = (
     'application/pdf',
@@ -169,15 +172,23 @@ class NoteSearchView(ListView):
         else:
             page = 0
 
-        index = SearchIndex()
+        try:
+            index = SearchIndex()
 
-        if 'course_id' in self.request.GET:
-            raw_results = index.search(self.request.GET['query'],
-                                              self.request.GET['course_id'],
-                                              page=page)
+            if 'course_id' in self.request.GET:
+                raw_results = index.search(self.request.GET['query'],
+                                                  self.request.GET['course_id'],
+                                                  page=page)
+            else:
+                raw_results = index.search(self.request.GET['query'],
+                                            page=page)
+
+        except Exception:
+            logger.error("Error with IndexDen:\n" + traceback.format_exc())
+            self.error = True
+            return Note.objects.none()
         else:
-            raw_results = index.search(self.request.GET['query'],
-                                        page=page)
+            self.error = False
 
         instances = Note.objects.in_bulk(raw_results.ordered_ids)
         results = []
@@ -195,6 +206,10 @@ class NoteSearchView(ListView):
         if 'course_id' in self.request.GET:
             kwargs['course'] = Course.objects.get(id=self.request.GET['course_id'])
 
+        if self.error:
+            kwargs['error'] = True
+            return super(NoteSearchView, self).get_context_data(**kwargs)
+
         # If query returned more search results than could
         # fit on one page, show "Next" button
         if self.has_more:
index 0cdb5fc3f2001e79ab9e9a9c8e9a523be719dd63..db328157be5fee2051edea7aaebaa1d1cecd70f3 100644 (file)
@@ -2,6 +2,6 @@
 # -*- coding:utf8 -*-
 # Copyright (C) 2013  FinalsClub Foundation
 from django.contrib import admin
-from karmaworld.apps.users.models import KarmaUser
+from karmaworld.apps.users.models import UserProfile
 
-admin.site.register(KarmaUser)
\ No newline at end of file
+admin.site.register(UserProfile)
\ No newline at end of file
diff --git a/karmaworld/apps/users/migrations/0002_auto__del_karmauser__add_userprofile.py b/karmaworld/apps/users/migrations/0002_auto__del_karmauser__add_userprofile.py
new file mode 100644 (file)
index 0000000..5780493
--- /dev/null
@@ -0,0 +1,98 @@
+# -*- 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):
+        # Deleting model 'KarmaUser'
+        db.delete_table('users_karmauser')
+
+        # Adding model 'UserProfile'
+        db.create_table('users_userprofile', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)),
+            ('school', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['courses.School'], null=True, blank=True)),
+            ('karma', self.gf('django.db.models.fields.IntegerField')(default=0)),
+        ))
+        db.send_create_signal('users', ['UserProfile'])
+
+
+    def backwards(self, orm):
+        # Adding model 'KarmaUser'
+        db.create_table('users_karmauser', (
+            ('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)),
+            ('school', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['courses.School'], null=True, blank=True)),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('karma', self.gf('django.db.models.fields.IntegerField')(default=0)),
+        ))
+        db.send_create_signal('users', ['KarmaUser'])
+
+        # Deleting model 'UserProfile'
+        db.delete_table('users_userprofile')
+
+
+    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'})
+        },
+        'courses.school': {
+            'Meta': {'ordering': "['-file_count', '-priority', 'name']", 'object_name': 'School'},
+            'alias': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'facebook_id': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'file_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'hashtag': ('django.db.models.fields.CharField', [], {'max_length': '16', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'priority': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '150', 'null': 'True'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '511', 'blank': 'True'}),
+            'usde_id': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'users.userprofile': {
+            'Meta': {'object_name': 'UserProfile'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'karma': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'school': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['courses.School']", 'null': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
+        }
+    }
+
+    complete_apps = ['users']
\ No newline at end of file
index 27aef53cbcdced3af14015b983ab0cec0fd54abc..42d97042e979bf5717770b3c048bc5fcb31ea0ce 100644 (file)
@@ -1,30 +1,27 @@
 #!/usr/bin/env python
 # -*- coding:utf8 -*-
 # Copyright (C) 2013  FinalsClub Foundation
-from django.contrib import admin
-
+from allauth.account.signals import user_logged_in
+from django.contrib.auth.models import User
+from django.db.models.signals import post_save
+from django.dispatch import receiver
 from django.db import models
+from karmaworld.apps.courses.models import School
 
 
-class KarmaUserManager(models.Manager):
-    """ Handle restoring data. """
-    def get_by_natural_key(self, email):
-        """
-        Return a KarmaUser defined by his/her email address.
-        """
-        return self.get(email=email)
-
+class UserProfile(models.Model):
+    user      = models.OneToOneField(User)
 
-class KarmaUser(models.Model):
-    objects = KarmaUserManager()
+    school    = models.ForeignKey(School, blank=True, null=True)
 
-    email   = models.EmailField(blank=False, null=False, unique=True)
+    karma     = models.IntegerField(default=0)
 
     def __unicode__(self):
-        return u'KarmaUser: {0}'.format(self.email)
+        return self.user.__unicode__()
+
+
+@receiver(post_save, sender=User, weak=True)
+def create_user_profile(sender, instance, created, **kwargs):
+    if created:
+        UserProfile.objects.create(user=instance)
 
-    def natural_key(self):
-        """
-        A KarmaUser is uniquely defined by his/her email address.
-        """
-        return (self.email,)
diff --git a/karmaworld/apps/users/views.py b/karmaworld/apps/users/views.py
new file mode 100644 (file)
index 0000000..1cfd9ac
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# -*- coding:utf8 -*-
+# Copyright (C) 2013  FinalsClub Foundation
+
+from django.contrib.auth.models import User
+from django.views.generic import TemplateView
+from django.views.generic.detail import SingleObjectMixin
+
+
+class ProfileView(TemplateView, SingleObjectMixin):
+    model = User
+    context_object_name = 'user' # name passed to template
+    template_name = 'user_profile.html'
+
+    def get_object(self, queryset=None):
+        u = self.request.user
+        return self.request.user
+
diff --git a/karmaworld/assets/css/accounts.css b/karmaworld/assets/css/accounts.css
new file mode 100644 (file)
index 0000000..2d70e5c
--- /dev/null
@@ -0,0 +1,39 @@
+#account_content
+{
+  padding-top: 46px;
+}
+
+h1
+{
+  margin-top: 1em;
+  text-align: center;
+  font-family: "MuseoSlab-500";
+  font-size: 36px;
+}
+
+h2
+{
+  margin-top: 1em;
+  text-align: center;
+  font-family: "MuseoSlab-500";
+  font-size: 24px;
+}
+
+ul
+{
+  list-style-type: none;
+}
+
+button,
+button:hover
+{
+  border: none;
+  background-color: white;
+  color: #f05a28;
+  cursor: pointer;
+  font: 30px/1.2em "MuseoSlab-300", serif;
+  margin: 1px;
+  text-align: center;
+  text-transform: uppercase;
+}
+
index e5703147db5416bee736337ee4e669d9342042a4..502ef733707844618d660bc10c90d06133b67c2a 100644 (file)
@@ -106,9 +106,11 @@ input[type="text"]:focus, textarea:focus
   cursor: pointer;
   padding-bottom: 16px;
 }
+
 #login_container a
 {
   float: right;
+  padding-left: 15px;
 }
 
 #login_container.clicked
@@ -852,3 +854,10 @@ legend
 .inline-form {
   border-bottom: 1px dashed #666;
 }
+
+/* Social account integration */
+
+.facebook-login-btn
+{
+  content: url(../img/facebook.png);
+}
diff --git a/karmaworld/assets/img/facebook.png b/karmaworld/assets/img/facebook.png
new file mode 100644 (file)
index 0000000..a9e2d57
Binary files /dev/null and b/karmaworld/assets/img/facebook.png differ
index bfc6fb199f563c1975aa8ae10e3eb0d74a0c7cc4..e7d55f7eefa5b95dd65f0727aeea9deebe6f1594 100644 (file)
@@ -136,6 +136,10 @@ TEMPLATE_CONTEXT_PROCESSORS = (
     'django.core.context_processors.tz',
     'django.contrib.messages.context_processors.messages',
     'django.core.context_processors.request',
+
+    # allauth specific context processors
+    "allauth.account.context_processors.account",
+    "allauth.socialaccount.context_processors.socialaccount",
 )
 
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
@@ -206,6 +210,15 @@ THIRD_PARTY_APPS = (
 
     # Tagging https://github.com/yedpodtrzitko/django-taggit
     'taggit',
+
+    'allauth',
+    'allauth.account',
+    'allauth.socialaccount',
+    # ... include the providers you want to enable:
+    'allauth.socialaccount.providers.facebook',
+    'allauth.socialaccount.providers.google',
+    'allauth.socialaccount.providers.openid',
+    'allauth.socialaccount.providers.twitter',
 )
 
 LOCAL_APPS = (
@@ -223,6 +236,35 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
 ########## END APP CONFIGURATION
 
 
+########## AUTHENTICATION
+
+AUTHENTICATION_BACKENDS = (
+    # Needed to login by username in Django admin, regardless of `allauth`
+    "django.contrib.auth.backends.ModelBackend",
+
+    # `allauth` specific authentication methods, such as login by e-mail
+    "allauth.account.auth_backends.AuthenticationBackend",
+)
+
+ACCOUNT_EMAIL_REQUIRED = True
+ACCOUNT_AUTHENTICATION_METHOD = "email"
+ACCOUNT_CONFIRM_EMAIL_ON_GET = True
+ACCOUNT_EMAIL_VERIFICATION = "optional"
+ACCOUNT_EMAIL_SUBJECT_PREFIX = "KarmaNotes.org"
+ACCOUNT_USER_MODEL_EMAIL_FIELD = "email"
+ACCOUNT_USERNAME_REQUIRED = False
+SOCIALACCOUNT_EMAIL_REQUIRED = True
+SOCIALACCOUNT_EMAIL_VERIFICATION = "optional"
+
+# You can't log in with a username,
+# you must use an authenitcation provider
+ACCOUNT_USER_MODEL_USERNAME_FIELD = None
+ACCOUNT_USER_MODEL_EMAIL_FIELD = None
+
+AUTH_PROFILE_MODULE = 'accounts.UserProfile'
+
+######### END AUTHENTICATION
+
 ########## LOGGING CONFIGURATION
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
 LOGGING = {
diff --git a/karmaworld/templates/account/account_inactive.html b/karmaworld/templates/account/account_inactive.html
new file mode 100644 (file)
index 0000000..3347f4f
--- /dev/null
@@ -0,0 +1,11 @@
+{% extends "account/base.html" %}
+
+{% load i18n %}
+
+{% block head_title %}{% trans "Account Inactive" %}{% endblock %}
+
+{% block content %}
+<h1>{% trans "Account Inactive" %}</h1>
+
+<p>{% trans "This account is inactive." %}</p>
+{% endblock %}
diff --git a/karmaworld/templates/account/base.html b/karmaworld/templates/account/base.html
new file mode 100644 (file)
index 0000000..d9e59dc
--- /dev/null
@@ -0,0 +1,66 @@
+{% load url from future %}
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+  <meta charset="utf-8" />
+  <meta name="viewport" content="width=device-width" />
+  <title>KarmaNotes -- {% block head_title %}{% endblock %}</title>
+       
+  <link rel="shortcut icon" href="{{ STATIC_URL }}img/favicon.ico">
+  <link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/fontface/fontface.css">
+  <link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}vendor/foundation-4.2.3/css/foundation.css">
+  <link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/global.css">
+  <link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/media.css">
+  <link rel="stylesheet" href="http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css">
+  <link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/accounts.css">
+  <!-- include Font Awesome -->
+  <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
+
+  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
+  <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
+  <script src="{{ STATIC_URL }}js/jquery-scrollto.js" ></script>
+
+  <!-- block pagescripts -->
+  {% block pagescripts %}
+  {% endblock %}
+  <!-- end block pagescripts -->
+
+  <!-- block pagestyle -->
+  {% block pagestyle %}
+  {% endblock %}
+  <!-- end block pagescripts -->
+
+  {% include 'partial/trackers.html' %}
+
+       
+</head>
+<body>
+
+<!-- include header -->
+{% include 'header.html' %}
+<!-- end include header -->
+
+<!-- block content -->
+
+<section id="account_content">
+  <div class="row">
+    <div class="small-12 columns center">
+      {% block content %}
+      {% endblock %}
+    </div>
+  </div>
+</section>
+
+<!-- end block content -->
+
+<!-- include footer-->
+{% include 'footer.html' %}
+<!-- end include footer-->
+
+<!-- block bodyscripts -->
+{% block extra_body %}
+{% endblock %}
+<!-- end block bodyscripts -->
+
+</body>
+</html>
diff --git a/karmaworld/templates/account/email.html b/karmaworld/templates/account/email.html
new file mode 100644 (file)
index 0000000..d25882e
--- /dev/null
@@ -0,0 +1,74 @@
+{% extends "account/base.html" %}
+
+{% load i18n %}
+{% load url from future %}
+
+{% block head_title %}{% trans "Account" %}{% endblock %}
+
+{% block content %}
+    <h1>{% trans "E-mail Addresses" %}</h1>
+{% if user.emailaddress_set.all %}
+<p>{% trans 'The following e-mail addresses are associated with your account:' %}</p>
+
+<form action="{% url 'account_email' %}" class="email_list" method="post">
+{% csrf_token %}
+<fieldset class="blockLabels">
+
+  {% for emailaddress in user.emailaddress_set.all %}
+<div class="ctrlHolder">
+      <label for="email_radio_{{forloop.counter}}" class="{% if emailaddress.primary %}primary_email{%endif%}">
+
+      <input id="email_radio_{{forloop.counter}}" type="radio" name="email" {% if emailaddress.primary %}checked="checked"{%endif %} value="{{emailaddress.email}}"/>
+
+{{ emailaddress.email }}
+    {% if emailaddress.verified %}
+    <span class="verified">{% trans "Verified" %}</span>
+    {% else %}
+    <span class="unverified">{% trans "Unverified" %}</span>
+    {% endif %}
+      {% if emailaddress.primary %}<span class="primary">{% trans "Primary" %}</span>{% endif %}
+</label>
+</div>
+  {% endfor %}
+
+<div class="buttonHolder">
+      <button class="secondaryAction" type="submit" name="action_primary" >{% trans 'Make Primary' %}</button>
+      <button class="secondaryAction" type="submit" name="action_send" >{% trans 'Re-send Verification' %}</button>
+      <button class="primaryAction" type="submit" name="action_remove" >{% trans 'Remove' %}</button>
+</div>
+
+</fieldset>
+</form>
+
+{% else %}
+<p><strong>{% trans 'Warning:'%}</strong> {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}</p>
+
+{% endif %}
+
+
+    <h2>{% trans "Add E-mail Address" %}</h2>
+
+    <form method="post" action="{% url 'account_email' %}" class="add_email">
+        {% csrf_token %}
+        {{ form.as_p}}
+        <button name="action_add" type="submit">{% trans "Add E-mail" %}</button>
+    </form>
+
+{% endblock %}
+
+
+{% block extra_body %}
+<script type="text/javascript">
+(function() {
+  var message = "{% trans 'Do you really want to remove the selected e-mail address?' %}";
+  var actions = document.getElementsByName('action_remove');
+  if (actions.length) {
+    actions[0].addEventListener("click", function(e) {
+      if (! confirm(message)) {
+        e.preventDefault();
+      }
+    });
+  }
+})();
+</script>
+{% endblock %}
diff --git a/karmaworld/templates/account/email/email_confirmation_message.txt b/karmaworld/templates/account/email/email_confirmation_message.txt
new file mode 100644 (file)
index 0000000..50bfb87
--- /dev/null
@@ -0,0 +1,4 @@
+{% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}{% blocktrans with current_site.name as site_name %}User {{ user_display }} at {{ site_name }} has given this as an email address.
+
+To confirm this is correct, go to {{ activate_url }}
+{% endblocktrans %}{% endautoescape %}
diff --git a/karmaworld/templates/account/email/email_confirmation_signup_message.txt b/karmaworld/templates/account/email/email_confirmation_signup_message.txt
new file mode 100644 (file)
index 0000000..9996f7e
--- /dev/null
@@ -0,0 +1 @@
+{% include "account/email/email_confirmation_message.txt" %}
diff --git a/karmaworld/templates/account/email/email_confirmation_signup_subject.txt b/karmaworld/templates/account/email/email_confirmation_signup_subject.txt
new file mode 100644 (file)
index 0000000..4c85ebb
--- /dev/null
@@ -0,0 +1 @@
+{% include "account/email/email_confirmation_subject.txt" %}
diff --git a/karmaworld/templates/account/email/email_confirmation_subject.txt b/karmaworld/templates/account/email/email_confirmation_subject.txt
new file mode 100644 (file)
index 0000000..3c960da
--- /dev/null
@@ -0,0 +1,4 @@
+{% load i18n %}
+{% autoescape off %}
+{% blocktrans %}Confirm E-mail Address{% endblocktrans %}
+{% endautoescape %}
diff --git a/karmaworld/templates/account/email/password_reset_key_message.txt b/karmaworld/templates/account/email/password_reset_key_message.txt
new file mode 100644 (file)
index 0000000..585e8b3
--- /dev/null
@@ -0,0 +1,9 @@
+{% load i18n %}{% blocktrans with site.domain as site_domain and user.username as username %}You're receiving this e-mail because you or someone else has requested a password for your user account at {{site_domain}}.
+It can be safely ignored if you did not request a password reset. Click the link below to reset your password.
+
+{{password_reset_url}}
+
+In case you forgot, your username is {{username}}.
+
+Thanks for using our site!
+{% endblocktrans %}
diff --git a/karmaworld/templates/account/email/password_reset_key_subject.txt b/karmaworld/templates/account/email/password_reset_key_subject.txt
new file mode 100644 (file)
index 0000000..aa80d11
--- /dev/null
@@ -0,0 +1,4 @@
+{% load i18n %}
+{% autoescape off %}
+{% blocktrans %}Password Reset E-mail{% endblocktrans %}
+{% endautoescape %}
\ No newline at end of file
diff --git a/karmaworld/templates/account/email_confirm.html b/karmaworld/templates/account/email_confirm.html
new file mode 100644 (file)
index 0000000..9d0d33e
--- /dev/null
@@ -0,0 +1,32 @@
+{% extends "account/base.html" %}
+
+{% load url from future %}
+{% load i18n %}
+{% load account %}
+
+{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %}
+
+
+{% block content %}
+<h1>{% trans "Confirm E-mail Address" %}</h1>
+
+{% if confirmation %}
+
+{% user_display confirmation.email_address.user as user_display %}
+        
+<p>{% blocktrans with confirmation.email_address.email as email %}Please confirm that <a href="mailto:{{email}}">{{ email }}</a> is an e-mail address for user {{ user_display }}.{% endblocktrans %}</p>
+
+<form method="post" action="{% url 'account_confirm_email' confirmation.key %}">
+{% csrf_token %}
+    <button type="submit">{% trans 'Confirm' %}</button>
+</form>
+
+{% else %}
+
+{% url 'account_email' as email_url %}
+
+<p>{% blocktrans %}This e-mail confirmation link expired or is invalid. Please <a href="{{ email_url}}">issue a new e-mail confirmation request</a>.{% endblocktrans %}</p>
+
+{% endif %}
+
+{% endblock %}
diff --git a/karmaworld/templates/account/email_confirmed.html b/karmaworld/templates/account/email_confirmed.html
new file mode 100644 (file)
index 0000000..bd498d3
--- /dev/null
@@ -0,0 +1,17 @@
+{% extends "account/base.html" %}
+
+{% load i18n %}
+{% load account %}
+
+{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %}
+
+
+{% block content %}
+
+<h1>{% trans "Confirm E-mail Address" %}</h1>
+
+{% user_display email_address.user as user_display %}
+        
+<p>{% blocktrans with email_address.email as email %}You have confirmed that <a href="mailto:{{email}}">{{ email }}</a> is an e-mail address for user {{ user_display }}.{% endblocktrans %}</p>
+
+{% endblock %}
diff --git a/karmaworld/templates/account/login.html b/karmaworld/templates/account/login.html
new file mode 100644 (file)
index 0000000..83cf279
--- /dev/null
@@ -0,0 +1,45 @@
+{% extends "account/base.html" %}
+
+{% load i18n %}
+{% load account %}
+{% load url from future %}
+
+{% block head_title %}{% trans "Sign In" %}{% endblock %}
+
+{% block content %}
+
+<h1>{% trans "Sign In" %}</h1>
+
+{% if socialaccount.providers  %}
+<p>{% blocktrans with site.name as site_name %}Please sign in with one
+of your existing third party accounts. Or, <a href="{{ signup_url }}">sign up</a>
+for a {{site_name}} account and sign in below:{% endblocktrans %}</p>
+
+<div class="socialaccount_ballot">
+
+  <ul class="socialaccount_providers">
+    {% include "socialaccount/snippets/provider_list.html" with process="login" %}
+  </ul>
+
+  <div class="login-or">{% trans 'or' %}</div>
+
+</div>
+
+{% include "socialaccount/snippets/login_extra.html" %}
+
+{% else %}
+<p>{% blocktrans %}If you have not created an account yet, then please
+<a href="{{ signup_url }}">sign up</a> first.{% endblocktrans %}</p>
+{% endif %}
+
+<form class="login" method="POST" action="{% url 'account_login' %}">
+  {% csrf_token %}
+  {{ form.as_p }}
+  {% if redirect_field_value %}
+  <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
+  {% endif %}
+  <a class="button secondaryAction" href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
+  <button class="primaryAction" type="submit">{% trans "Sign In" %}</button>
+</form>
+
+{% endblock %}
diff --git a/karmaworld/templates/account/logout.html b/karmaworld/templates/account/logout.html
new file mode 100644 (file)
index 0000000..4447ee8
--- /dev/null
@@ -0,0 +1,22 @@
+{% extends "account/base.html" %}
+
+{% load url from future %}
+{% load i18n %}
+
+{% block head_title %}{% trans "Sign Out" %}{% endblock %}
+
+{% block content %}
+<h1>{% trans "Sign Out" %}</h1>
+
+<p>{% trans 'Are you sure you want to sign out?' %}</p>
+
+<form method="post" action="{% url 'account_logout' %}">
+  {% csrf_token %}
+  {% if redirect_field_value %}
+  <input type="hidden" name="{{redirect_field_name}}" value="{{redirect_field_value}}"/>
+  {% endif %}
+  <button type="submit">{% trans 'Sign Out' %}</button>
+</form>
+
+
+{% endblock %}
diff --git a/karmaworld/templates/account/messages/cannot_delete_primary_email.txt b/karmaworld/templates/account/messages/cannot_delete_primary_email.txt
new file mode 100644 (file)
index 0000000..de55571
--- /dev/null
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}You cannot remove your primary e-mail address ({{email}}).{% endblocktrans %}
diff --git a/karmaworld/templates/account/messages/email_confirmation_sent.txt b/karmaworld/templates/account/messages/email_confirmation_sent.txt
new file mode 100644 (file)
index 0000000..7a526f8
--- /dev/null
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}Confirmation e-mail sent to {{email}}.{% endblocktrans %}
diff --git a/karmaworld/templates/account/messages/email_confirmed.txt b/karmaworld/templates/account/messages/email_confirmed.txt
new file mode 100644 (file)
index 0000000..3427a4d
--- /dev/null
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}You have confirmed {{email}}.{% endblocktrans %}
diff --git a/karmaworld/templates/account/messages/email_deleted.txt b/karmaworld/templates/account/messages/email_deleted.txt
new file mode 100644 (file)
index 0000000..5cf7cf9
--- /dev/null
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}Removed e-mail address {{email}}.{% endblocktrans %}
diff --git a/karmaworld/templates/account/messages/logged_in.txt b/karmaworld/templates/account/messages/logged_in.txt
new file mode 100644 (file)
index 0000000..f49248a
--- /dev/null
@@ -0,0 +1,4 @@
+{% load account %}
+{% load i18n %}
+{% user_display user as name %}
+{% blocktrans %}Successfully signed in as {{name}}.{% endblocktrans %}
diff --git a/karmaworld/templates/account/messages/logged_out.txt b/karmaworld/templates/account/messages/logged_out.txt
new file mode 100644 (file)
index 0000000..2cd4627
--- /dev/null
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}You have signed out.{% endblocktrans %}
diff --git a/karmaworld/templates/account/messages/password_changed.txt b/karmaworld/templates/account/messages/password_changed.txt
new file mode 100644 (file)
index 0000000..e01766b
--- /dev/null
@@ -0,0 +1,3 @@
+{% load i18n %}
+{% blocktrans %}Password successfully changed.{% endblocktrans %}
+
diff --git a/karmaworld/templates/account/messages/password_set.txt b/karmaworld/templates/account/messages/password_set.txt
new file mode 100644 (file)
index 0000000..e36cef8
--- /dev/null
@@ -0,0 +1,3 @@
+{% load i18n %}
+{% blocktrans %}Password successfully set.{% endblocktrans %}
+
diff --git a/karmaworld/templates/account/messages/primary_email_set.txt b/karmaworld/templates/account/messages/primary_email_set.txt
new file mode 100644 (file)
index 0000000..b6a70dd
--- /dev/null
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}Primary e-mail address set.{% endblocktrans %}
diff --git a/karmaworld/templates/account/messages/unverified_primary_email.txt b/karmaworld/templates/account/messages/unverified_primary_email.txt
new file mode 100644 (file)
index 0000000..9c9d0d8
--- /dev/null
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}Your primary e-mail address must be verified.{% endblocktrans %}
diff --git a/karmaworld/templates/account/password_change.html b/karmaworld/templates/account/password_change.html
new file mode 100644 (file)
index 0000000..c6daea1
--- /dev/null
@@ -0,0 +1,16 @@
+{% extends "account/base.html" %}
+
+{% load url from future %}
+{% load i18n %}
+
+{% block head_title %}{% trans "Change Password" %}{% endblock %}
+
+{% block content %}
+    <h1>{% trans "Change Password" %}</h1>
+
+    <form method="POST" action="{% url 'account_change_password' %}" class="password_change">
+        {% csrf_token %}
+        {{ form.as_p }}
+        <button type="submit" name="action">{% trans "Change Password" %}</button>
+    </form>
+{% endblock %}
diff --git a/karmaworld/templates/account/password_reset.html b/karmaworld/templates/account/password_reset.html
new file mode 100644 (file)
index 0000000..1616960
--- /dev/null
@@ -0,0 +1,30 @@
+{% extends "account/base.html" %}
+
+{% load i18n %}
+{% load account %}
+
+{% block head_title %}{% trans "Password Reset" %}{% endblock %}
+
+{% block content %}
+
+    <h1>{% trans "Password Reset" %}</h1>
+    {% if user.is_authenticated %}
+    {% include "account/snippets/already_logged_in.html" %}
+    {% endif %}
+    
+    <p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}</p>
+    
+    <form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
+        {% csrf_token %}
+        {{ form.as_p }}
+        <input type="submit" value="{% trans "Reset My Password" %}" />
+    </form>
+    
+    <p>{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}</p>
+{% endblock %}
+
+{% block extra_body %}
+    <script>
+        $("#id_email").focus();
+    </script>
+{% endblock %}
diff --git a/karmaworld/templates/account/password_reset_done.html b/karmaworld/templates/account/password_reset_done.html
new file mode 100644 (file)
index 0000000..e90504f
--- /dev/null
@@ -0,0 +1,16 @@
+{% extends "account/base.html" %}
+
+{% load i18n %}
+{% load account %}
+
+{% block head_title %}{% trans "Password Reset" %}{% endblock %}
+
+{% block content %}
+    <h1>{% trans "Password Reset" %}</h1>
+    
+    {% if user.is_authenticated %}
+    {% include "account/snippets/already_logged_in.html" %}
+    {% endif %}
+    
+    <p>{% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}</p>
+{% endblock %}
diff --git a/karmaworld/templates/account/password_reset_from_key.html b/karmaworld/templates/account/password_reset_from_key.html
new file mode 100644 (file)
index 0000000..e30b5a8
--- /dev/null
@@ -0,0 +1,24 @@
+{% extends "account/base.html" %}
+
+{% load url from future %}
+{% load i18n %}
+{% block head_title %}{% trans "Change Password" %}{% endblock %}
+
+{% block content %}
+    <h1>{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}</h1>
+
+    {% if token_fail %}
+        {% url 'account_reset_password' as passwd_reset_url %}
+        <p>{% blocktrans %}The password reset link was invalid, possibly because it has already been used.  Please request a <a href="{{ passwd_reset_url }}">new password reset</a>.{% endblocktrans %}</p>
+    {% else %}
+        {% if form %}
+            <form method="POST" action=".">
+                {% csrf_token %}
+                {{ form.as_p }}
+                <input type="submit" name="action" value="{% trans "change password" %}"/>
+            </form>
+        {% else %}
+            <p>{% trans 'Your password is now changed.' %}</p>
+        {% endif %}
+    {% endif %}
+{% endblock %}
diff --git a/karmaworld/templates/account/password_reset_from_key_done.html b/karmaworld/templates/account/password_reset_from_key_done.html
new file mode 100644 (file)
index 0000000..25608ad
--- /dev/null
@@ -0,0 +1,10 @@
+{% extends "account/base.html" %}
+
+{% load url from future %}
+{% load i18n %}
+{% block head_title %}{% trans "Change Password" %}{% endblock %}
+
+{% block content %}
+    <h1>{% trans "Change Password" %}</h1>
+    <p>{% trans 'Your password is now changed.' %}</p>
+{% endblock %}
diff --git a/karmaworld/templates/account/password_set.html b/karmaworld/templates/account/password_set.html
new file mode 100644 (file)
index 0000000..dbb4ef9
--- /dev/null
@@ -0,0 +1,16 @@
+{% extends "account/base.html" %}
+
+{% load url from future %}
+{% load i18n %}
+
+{% block head_title %}{% trans "Set Password" %}{% endblock %}
+
+{% block content %}
+    <h1>{% trans "Set Password" %}</h1>
+
+    <form method="POST" action="{% url 'account_set_password' %}" class="password_set">
+        {% csrf_token %}
+        {{ form.as_p }}
+        <input type="submit" name="action" value="{% trans "Set Password" %}"/>
+    </form>
+{% endblock %}
diff --git a/karmaworld/templates/account/signup.html b/karmaworld/templates/account/signup.html
new file mode 100644 (file)
index 0000000..71d6956
--- /dev/null
@@ -0,0 +1,25 @@
+{% extends "account/base.html" %}
+
+{% load url from future %}
+{% load i18n %}
+
+{% block head_title %}{% trans "Signup" %}{% endblock %}
+
+{% block content %}
+<h1>{% trans "Sign Up" %}</h1>
+
+<p>{% blocktrans %}Already have an account? Then please <a href="{{ login_url }}">sign in</a>.{% endblocktrans %}</p>
+
+<form class="signup" id="signup_form" method="post" action="{% url 'account_signup' %}">
+  {% csrf_token %}
+  {{ form.as_p }}
+  {% if redirect_field_value %}
+  <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
+  {% endif %}
+  <button type="submit">{% trans "Sign Up" %} &raquo;</button>
+</form>
+
+
+{% endblock %}
+
+
diff --git a/karmaworld/templates/account/signup_closed.html b/karmaworld/templates/account/signup_closed.html
new file mode 100644 (file)
index 0000000..5ef0709
--- /dev/null
@@ -0,0 +1,14 @@
+{% extends "account/base.html" %}
+
+{% load url from future %}
+{% load i18n %}
+
+{% block head_title %}{% trans "Sign Up Closed" %}{% endblock %}
+
+{% block content %}
+<h1>{% trans "Sign Up Closed" %}</h1>
+
+<p>{% trans "We are sorry, but the sign up is currently closed." %}</p>
+{% endblock %}
+
+
diff --git a/karmaworld/templates/account/snippets/already_logged_in.html b/karmaworld/templates/account/snippets/already_logged_in.html
new file mode 100644 (file)
index 0000000..00799f0
--- /dev/null
@@ -0,0 +1,5 @@
+{% load i18n %}
+{% load account %}
+
+{% user_display user as user_display %}
+<p><strong>{% trans "Note" %}:</strong> {% blocktrans %}you are already logged in as {{ user_display }}.{% endblocktrans %}</p>
diff --git a/karmaworld/templates/account/verification_sent.html b/karmaworld/templates/account/verification_sent.html
new file mode 100644 (file)
index 0000000..5f71331
--- /dev/null
@@ -0,0 +1,12 @@
+{% extends "account/base.html" %}
+
+{% load i18n %}
+
+{% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %}
+
+{% block content %}
+    <h1>{% trans "Verify Your E-mail Address" %}</h1>
+
+    <p>{% blocktrans %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}</p>
+
+{% endblock %}
diff --git a/karmaworld/templates/account/verified_email_required.html b/karmaworld/templates/account/verified_email_required.html
new file mode 100644 (file)
index 0000000..19fff58
--- /dev/null
@@ -0,0 +1,24 @@
+{% extends "account/base.html" %}
+
+{% load url from future %}
+{% load i18n %}
+
+{% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %}
+
+{% block content %}
+<h1>{% trans "Verify Your E-mail Address" %}</h1>
+
+{% url 'account_email' as email_url %}
+
+<p>{% blocktrans %}This part of the site requires us to verify that
+you are who you claim to be. For this purpose, we require that you
+verify ownership of your e-mail address. {% endblocktrans %}</p>
+
+<p>{% blocktrans %}We have sent an e-mail to you for
+verification. Please click on the link inside this e-mail. Please
+contact us if you do not receive it within a few minutes.{% endblocktrans %}</p>
+
+<p>{% blocktrans %}<strong>Note:</strong> you can still <a href="{{email_url}}">change your e-mail address</a>.{% endblocktrans %}</p>
+
+
+{% endblock %}
index 8fb12f835cf13bdc298a55d0ffcb4c94fc4beae0..204824ad43746779352f688d055d29f52e72638d 100644 (file)
   {% endblock %}
   <!-- end block pagescripts -->
 
-  <script type="text/javascript">
-    if (document.location.host === 'www.karmanotes.org' ||
-        document.location.host === 'karmanotes.org') {
-      var _gaq = _gaq || [];
-      _gaq.push(['_setAccount', 'UA-36897868-1']);
-      _gaq.push(['_setDomainName', 'karmanotes.org']);
-      _gaq.push(['_trackPageview']);
-
-      (function() {
-        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
-        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
-        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
-      })();
-      }
-  </script>
-
-  <script type='text/javascript'>
-    if (document.location.host === 'www.karmanotes.org' ||
-        document.location.host === 'karmanotes.org') {
-      window.__wtw_lucky_site_id = 17613;
-
-      (function() {
-        var wa = document.createElement('script'); wa.type = 'text/javascript'; wa.async = true;
-        wa.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://ca17613') + '.luckyorange.com/w.js';
-        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(wa, s);
-        })();
-      }
-  </script>
+  {% include 'partial/trackers.html' %}
 
        
 </head>
index 570e4abd4c5cb31b17e63c35457a2e494972ca8f..502cda2b4688f55a67b6721e96e6e898bbce2e3d 100644 (file)
       </div>
     {% endif %}
 
-    <div id="login_container" class="small-1 columns">
-      <a class=white href="{% url 'about' %}">About</a>
+    <div id="login_container" class="small-2 columns">
+      {% if request.user.is_authenticated %}
+        <a class="white" href="{% url 'account_logout' %}">Log Out</a>
+      {% else %}
+        <a class="white" href="{% url 'account_login' %}">Log In</a>
+      {% endif %}
+      <a class="white" href="{% url 'about' %}">About</a>
     </div>
 
   </div>
index 65e408bf48794cf3e9f2bdac0e1f6d79c845353a..a9bd52b05fbbbf9027042a4d709d66217abf9f75 100644 (file)
@@ -62,6 +62,9 @@
         {% else %}
           <div id="no_results" class="small-12 columns center column">
             <h4>Sorry! No results were found.</h4>
+            {% if error %}
+              <h4>There was an error with your search.</h4>
+            {% endif %}
           </div>
         {% endif %}
     </div>
index 44575ed1715866a193f966a0fcc5035bd22ec1ab..d675074390fcae064cc92190541a2cde80ae55ec 100644 (file)
@@ -1,4 +1,5 @@
 {% load url from future %}
+{% load socialaccount_tags %}
 <section id=filepicker-form class="extend-form">
 <!-- Javascript -->
 <script type="text/javascript" src="//api.filepicker.io/v1/filepicker.js"></script>
         <p>Thank you for sharing:</p>
         <ul id="uploaded_files">
         </ul>
-        <p>Your files are being processed and should be viewable within a few minutes.</p>
-        <p>If you'd like to share again, please <a href="">click here</a>.</p>
+        {% if not request.user.is_authenticated %}
+          <p>We'd love to have you sign up so you can claim the notes you just uploaded, and
+          build a reputation for uploading great notes.</p>
+          <p><a href='{% provider_login_url "facebook" %}'>
+            <span class="facebook-login-btn"></span>
+            </a></p>
+        {% endif %}
       </div>
     </div>
 
       </form>
     </div>
   </div>
-  <div id="save-btn-wrapper" class="hide row">
-    <div class="small-10 small-offset-1 columns">
-        <div class="row" style="display: inline;">
-          <form class="inline-form" method="POST" action="{% url 'upload_post' %}">
-            <div class="small-11 large-6 columns">
-                <legend>Your Email Address (Optional)</legend>
-                <input type="text" class="intext" id="id_email" name="email"
-                      placeholder="">
-              </div>
-          </form>
-        </div>
-        <div class="row">
-          <div class="small-8 large-6 columns">
-            <div id="save-btn" class="action-btn">
-              <i class="fa fa-save"></i> Save
-            </div>
-          </div>
-        </div>
+  <div class="small-8 small-offset-3 columns large-2">
+    <div id="save-btn" class="hide">
+      <i class="fa fa-save"></i> Save
     </div>
   </div>
 
             $(this).parent().parent().remove();
         });
 
-        $('#save-btn-wrapper').show();
+        $('#save-btn').show();
       }
 
       $('#save-btn').on('click', function(e){
                   $('#uploaded_files').append($('<li>', {text: uploaded_files[i]}));
                 }
                 $('#success').show();
-                $('#save-btn-wrapper').hide();
+                $('#save-btn').hide();
                 $('#forms_container .inline-form').remove();
                 if (document.location.host === 'www.karmanotes.org' ||
                   document.location.host === 'karmanotes.org') {
                   _gat._getTracker()._trackEvent('upload', 'upload form submitted');
                 }
-                setTimeout(function(){
-                  location.reload(true);
-                }, 15000);
               }
             });
             // Add the name we've just uploaded to the list
diff --git a/karmaworld/templates/partial/trackers.html b/karmaworld/templates/partial/trackers.html
new file mode 100644 (file)
index 0000000..1ca2b09
--- /dev/null
@@ -0,0 +1,29 @@
+
+<script type="text/javascript">
+  if (document.location.host === 'www.karmanotes.org' ||
+      document.location.host === 'karmanotes.org') {
+    var _gaq = _gaq || [];
+    _gaq.push(['_setAccount', 'UA-36897868-1']);
+    _gaq.push(['_setDomainName', 'karmanotes.org']);
+    _gaq.push(['_trackPageview']);
+
+    (function() {
+      var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+      ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+      var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+    })();
+    }
+</script>
+
+<script type='text/javascript'>
+  if (document.location.host === 'www.karmanotes.org' ||
+      document.location.host === 'karmanotes.org') {
+    window.__wtw_lucky_site_id = 17613;
+
+    (function() {
+      var wa = document.createElement('script'); wa.type = 'text/javascript'; wa.async = true;
+      wa.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://ca17613') + '.luckyorange.com/w.js';
+      var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(wa, s);
+      })();
+    }
+</script>
diff --git a/karmaworld/templates/socialaccount/authentication_error.html b/karmaworld/templates/socialaccount/authentication_error.html
new file mode 100644 (file)
index 0000000..0300295
--- /dev/null
@@ -0,0 +1,11 @@
+{% extends "socialaccount/base.html" %}
+
+{% load i18n %}
+
+{% block head_title %}{% trans "Social Network Login Failure" %}{% endblock %}
+
+{% block content %}
+<h1>{% trans "Social Network Login Failure" %}</h1>
+
+<p>{% trans "An error occurred while attempting to login via your social network account." %}</p>
+{% endblock %}
diff --git a/karmaworld/templates/socialaccount/base.html b/karmaworld/templates/socialaccount/base.html
new file mode 100644 (file)
index 0000000..18530d1
--- /dev/null
@@ -0,0 +1,2 @@
+{% extends "account/base.html" %}
+
diff --git a/karmaworld/templates/socialaccount/connections.html b/karmaworld/templates/socialaccount/connections.html
new file mode 100644 (file)
index 0000000..def62e1
--- /dev/null
@@ -0,0 +1,55 @@
+{% extends "socialaccount/base.html" %}
+
+{% load i18n %}
+{% load url from future %}
+
+{% block head_title %}{% trans "Account Connections" %}{% endblock %}
+
+{% block content %}
+<h1>{% trans "Account Connections" %}</h1>
+
+{% if form.accounts %}
+<p>{% blocktrans %}You can sign in to your account using any of the following third party accounts:{% endblocktrans %}</p>
+
+
+<form method="post" action="{% url 'socialaccount_connections' %}">
+{% csrf_token %}
+
+<fieldset>
+{% if form.non_field_errors %}
+<div id="errorMsg">{{form.non_field_errors}}</div>
+{% endif %}
+
+{% for base_account in form.accounts %}
+{% with base_account.get_provider_account as account %}
+<div>
+<label for="id_account_{{base_account.id}}">
+<input id="id_account_{{base_account.id}}" type="radio" name="account" value="{{base_account.id}}"/>
+<span class="socialaccount_provider {{base_account.provider}} {{account.get_brand.id}}">{{account.get_brand.name}}</span>
+{{account}}
+</label>
+</div>
+{% endwith %}
+{% endfor %}
+
+<div>
+<button type="submit">{% trans 'Remove' %}</button>
+</div>
+
+</fieldset>
+
+</form>
+
+{% else %}
+<p>{% trans 'You currently have no social network accounts connected to this account.' %}</p>
+{% endif %}
+
+<h2>{% trans 'Add a 3rd Party Account' %}</h2>
+
+<ul class="socialaccount_providers">
+{% include "socialaccount/snippets/provider_list.html" with process="connect" %}
+</ul>
+
+{% include "socialaccount/snippets/login_extra.html" %}
+
+{% endblock %}
diff --git a/karmaworld/templates/socialaccount/login_cancelled.html b/karmaworld/templates/socialaccount/login_cancelled.html
new file mode 100644 (file)
index 0000000..f73a769
--- /dev/null
@@ -0,0 +1,17 @@
+{% extends "socialaccount/base.html" %}
+
+{% load url from future %}
+{% load i18n %}
+
+{% block head_title %}{% trans "Login Cancelled" %}{% endblock %}
+
+{% block content %}
+    
+<h1>{% trans "Login Cancelled" %}</h1>
+
+{% url 'account_login' as login_url %}
+
+<p>{% blocktrans %}You decided to cancel logging in to our site using one of your existing accounts. If this was a mistake, please proceed to <a href="{{login_url}}">sign in</a>.{% endblocktrans %}</p>
+
+{% endblock %}
+
diff --git a/karmaworld/templates/socialaccount/messages/account_connected.txt b/karmaworld/templates/socialaccount/messages/account_connected.txt
new file mode 100644 (file)
index 0000000..be6aa60
--- /dev/null
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}The social account has been connected.{% endblocktrans %}
diff --git a/karmaworld/templates/socialaccount/messages/account_connected_other.txt b/karmaworld/templates/socialaccount/messages/account_connected_other.txt
new file mode 100644 (file)
index 0000000..e90f6cc
--- /dev/null
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}The social account is already connected to a different account.{% endblocktrans %}
diff --git a/karmaworld/templates/socialaccount/messages/account_disconnected.txt b/karmaworld/templates/socialaccount/messages/account_disconnected.txt
new file mode 100644 (file)
index 0000000..fd43f30
--- /dev/null
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}The social account has been disconnected.{% endblocktrans %}
diff --git a/karmaworld/templates/socialaccount/signup.html b/karmaworld/templates/socialaccount/signup.html
new file mode 100644 (file)
index 0000000..435b86c
--- /dev/null
@@ -0,0 +1,24 @@
+{% extends "socialaccount/base.html" %}
+{% load url from future %}
+
+{% load i18n %}
+
+{% block head_title %}{% trans "Signup" %}{% endblock %}
+
+{% block content %}
+    <h1>{% trans "Sign Up" %}</h1>
+
+<p>{% blocktrans with provider_name=account.get_provider.name site_name=site.name %}You are about to use your {{provider_name}} account to login to
+{{site_name}}. As a final step, please complete the following form:{% endblocktrans %}</p>
+
+<form class="signup" id="signup_form" method="post" action="{% url 'socialaccount_signup' %}">
+  {% csrf_token %}
+  {{ form.as_p }}
+  {% if redirect_field_value %}
+  <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
+  {% endif %}
+  <button type="submit">{% trans "Sign Up" %} &raquo;</button>
+</form>
+
+
+{% endblock %}
diff --git a/karmaworld/templates/socialaccount/snippets/login_extra.html b/karmaworld/templates/socialaccount/snippets/login_extra.html
new file mode 100644 (file)
index 0000000..f2c5225
--- /dev/null
@@ -0,0 +1,4 @@
+{% load socialaccount %}
+
+{% providers_media_js %}
+
diff --git a/karmaworld/templates/socialaccount/snippets/provider_list.html b/karmaworld/templates/socialaccount/snippets/provider_list.html
new file mode 100644 (file)
index 0000000..202c5c2
--- /dev/null
@@ -0,0 +1,19 @@
+{% load socialaccount %}
+
+{% for provider in socialaccount.providers %}
+{% if provider.id == "openid" %}
+{% for brand in provider.get_brands %}
+<li>
+  <a title="{{brand.name}}" 
+     class="socialaccount_provider {{provider.id}} {{brand.id}}" 
+     href="{% provider_login_url provider.id openid=brand.openid_url process=process %}"
+     >{{brand.name}}</a>
+</li>
+{% endfor %}
+{% endif %}
+<li>
+  <a title="{{provider.name}}" class="socialaccount_provider {{provider.id}}" 
+     href="{% provider_login_url provider.id process=process %}">{{provider.name}}</a>
+</li>
+{% endfor %}
+
diff --git a/karmaworld/templates/user_profile.html b/karmaworld/templates/user_profile.html
new file mode 100644 (file)
index 0000000..219a70b
--- /dev/null
@@ -0,0 +1,26 @@
+{% extends "account/base.html" %}
+{% load url from future %}
+{% load account %}
+
+{% block head_title %}
+  Your KarmaNotes Profile
+{% endblock %}
+
+{% block content %}
+  <section id="account_content">
+
+    <div class="row">
+      <div class="small-12 columns">
+        <h1>Hello there, {% user_display user %}.</h1>
+        <p>Here are the notes that you've uploaded:</p>
+        <ul>
+          {% for note in user.note_set.all %}
+            <li><a href="{{ note.get_absolute_url }}">{{ note.name }}</a></li>
+          {% endfor %}
+        </ul>
+      </div>
+    </div>
+
+
+  </section><!--/about_content-->
+{% endblock %}
index 7a88d16ff7a99aeef20ae385fd154601c1b4bd89..1718cfc9b7646ab88abbb573ace4a571a6ed656a 100644 (file)
@@ -15,14 +15,16 @@ from karmaworld.apps.courses.views import CourseListView
 from karmaworld.apps.courses.views import school_list
 from karmaworld.apps.courses.views import school_course_list
 from karmaworld.apps.courses.views import school_course_instructor_list
-from karmaworld.apps.notes.models import Note
 from karmaworld.apps.notes.views import NoteView, thank_note, NoteSearchView, flag_note
 from karmaworld.apps.notes.views import RawNoteDetailView
 from karmaworld.apps.notes.views import PDFView
 from karmaworld.apps.moderation import moderator
 from karmaworld.apps.document_upload.views import save_fp_upload
+from karmaworld.apps.users.views import ProfileView
 
 # See: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#hooking-adminsite-instances-into-your-urlconf
+
+
 admin.autodiscover()
 
 # reused named regex capture groups
@@ -60,6 +62,10 @@ urlpatterns = patterns('',
     url(r'^terms/$', direct_to_template, { 'template': 'terms.html' }, name='terms'),
     url(r'^about/$', AboutView.as_view(), name='about'),
 
+    # All Auth
+    url(r'^accounts/', include('allauth.urls')),
+    url(r'^accounts/profile/', ProfileView.as_view(), name='accounts_profile'),
+
     # VIEW for viewing a Note's gdrive generated html, used as iframe
     url(r'^raw/(?P<pk>\d+)$', RawNoteDetailView.as_view(), name='note_raw'),
     #url(r'^pdfview$', PDFView.as_view(), name='pdf'),
index 9bdfd57b81b0a838c04c49712f82185fdd7e403f..90fac6444231d4c92674c32d8bb8900f1c134de6 100644 (file)
@@ -20,3 +20,4 @@ python-twitter
 gdshortener
 git+https://github.com/flaptor/indextank-py.git
 html2text
+django-allauth