From: Charles Connell Date: Thu, 9 Jan 2014 21:04:51 +0000 (-0500) Subject: First go at a user system #240 #255 #256 X-Git-Tag: release-20150131~289 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=fe3879edc21a599b51f594e653d4da2adb5f6f88;p=oweals%2Fkarmaworld.git First go at a user system #240 #255 #256 --- diff --git a/karmaworld/apps/document_upload/models.py b/karmaworld/apps/document_upload/models.py index 3034ce4..841b33e 100644 --- a/karmaworld/apps/document_upload/models.py +++ b/karmaworld/apps/document_upload/models.py @@ -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) diff --git a/karmaworld/apps/document_upload/tasks.py b/karmaworld/apps/document_upload/tasks.py index a09467d..bb8f661 100644 --- a/karmaworld/apps/document_upload/tasks.py +++ b/karmaworld/apps/document_upload/tasks.py @@ -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()) diff --git a/karmaworld/apps/document_upload/tests.py b/karmaworld/apps/document_upload/tests.py index 843aa99..cf36a5e 100644 --- a/karmaworld/apps/document_upload/tests.py +++ b/karmaworld/apps/document_upload/tests.py @@ -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) + + + diff --git a/karmaworld/apps/document_upload/views.py b/karmaworld/apps/document_upload/views.py index 58a83e2..f2a38c9 100644 --- a/karmaworld/apps/document_upload/views.py +++ b/karmaworld/apps/document_upload/views.py @@ -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) diff --git a/karmaworld/apps/moderation/admin.py b/karmaworld/apps/moderation/admin.py index 4580c0f..58104cc 100644 --- a/karmaworld/apps/moderation/admin.py +++ b/karmaworld/apps/moderation/admin.py @@ -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 diff --git a/karmaworld/apps/notes/gdrive.py b/karmaworld/apps/notes/gdrive.py index 68359c9..42bc11a 100644 --- a/karmaworld/apps/notes/gdrive.py +++ b/karmaworld/apps/notes/gdrive.py @@ -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 index 0000000..2f71f2d --- /dev/null +++ b/karmaworld/apps/notes/migrations/0012_auto__chg_field_note_user.py @@ -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 diff --git a/karmaworld/apps/notes/models.py b/karmaworld/apps/notes/models.py index 57266b7..f9f281a 100644 --- a/karmaworld/apps/notes/models.py +++ b/karmaworld/apps/notes/models.py @@ -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() diff --git a/karmaworld/apps/notes/search.py b/karmaworld/apps/notes/search.py index d578f5d..4a5869f 100644 --- a/karmaworld/apps/notes/search.py +++ b/karmaworld/apps/notes/search.py @@ -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 diff --git a/karmaworld/apps/notes/views.py b/karmaworld/apps/notes/views.py index 4cd3e3e..322aa00 100644 --- a/karmaworld/apps/notes/views.py +++ b/karmaworld/apps/notes/views.py @@ -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: diff --git a/karmaworld/apps/users/admin.py b/karmaworld/apps/users/admin.py index 0cdb5fc..db32815 100644 --- a/karmaworld/apps/users/admin.py +++ b/karmaworld/apps/users/admin.py @@ -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 index 0000000..5780493 --- /dev/null +++ b/karmaworld/apps/users/migrations/0002_auto__del_karmauser__add_userprofile.py @@ -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 diff --git a/karmaworld/apps/users/models.py b/karmaworld/apps/users/models.py index 27aef53..42d9704 100644 --- a/karmaworld/apps/users/models.py +++ b/karmaworld/apps/users/models.py @@ -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 index 0000000..1cfd9ac --- /dev/null +++ b/karmaworld/apps/users/views.py @@ -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 index 0000000..2d70e5c --- /dev/null +++ b/karmaworld/assets/css/accounts.css @@ -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; +} + diff --git a/karmaworld/assets/css/global.css b/karmaworld/assets/css/global.css index e570314..502ef73 100644 --- a/karmaworld/assets/css/global.css +++ b/karmaworld/assets/css/global.css @@ -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 index 0000000..a9e2d57 Binary files /dev/null and b/karmaworld/assets/img/facebook.png differ diff --git a/karmaworld/settings/common.py b/karmaworld/settings/common.py index bfc6fb1..e7d55f7 100644 --- a/karmaworld/settings/common.py +++ b/karmaworld/settings/common.py @@ -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 index 0000000..3347f4f --- /dev/null +++ b/karmaworld/templates/account/account_inactive.html @@ -0,0 +1,11 @@ +{% extends "account/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Account Inactive" %}{% endblock %} + +{% block content %} +

{% trans "Account Inactive" %}

+ +

{% trans "This account is inactive." %}

+{% endblock %} diff --git a/karmaworld/templates/account/base.html b/karmaworld/templates/account/base.html new file mode 100644 index 0000000..d9e59dc --- /dev/null +++ b/karmaworld/templates/account/base.html @@ -0,0 +1,66 @@ +{% load url from future %} + + + + + + KarmaNotes -- {% block head_title %}{% endblock %} + + + + + + + + + + + + + + + + + {% block pagescripts %} + {% endblock %} + + + + {% block pagestyle %} + {% endblock %} + + + {% include 'partial/trackers.html' %} + + + + + + +{% include 'header.html' %} + + + + +
+
+
+ {% block content %} + {% endblock %} +
+
+
+ + + + +{% include 'footer.html' %} + + + +{% block extra_body %} +{% endblock %} + + + + diff --git a/karmaworld/templates/account/email.html b/karmaworld/templates/account/email.html new file mode 100644 index 0000000..d25882e --- /dev/null +++ b/karmaworld/templates/account/email.html @@ -0,0 +1,74 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% load url from future %} + +{% block head_title %}{% trans "Account" %}{% endblock %} + +{% block content %} +

{% trans "E-mail Addresses" %}

+{% if user.emailaddress_set.all %} +

{% trans 'The following e-mail addresses are associated with your account:' %}

+ +
+{% csrf_token %} +
+ + {% for emailaddress in user.emailaddress_set.all %} +
+ +
+ {% endfor %} + +
+ + + +
+ +
+
+ +{% else %} +

{% trans 'Warning:'%} {% 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." %}

+ +{% endif %} + + +

{% trans "Add E-mail Address" %}

+ +
+ {% csrf_token %} + {{ form.as_p}} + +
+ +{% endblock %} + + +{% block extra_body %} + +{% 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 index 0000000..50bfb87 --- /dev/null +++ b/karmaworld/templates/account/email/email_confirmation_message.txt @@ -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 index 0000000..9996f7e --- /dev/null +++ b/karmaworld/templates/account/email/email_confirmation_signup_message.txt @@ -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 index 0000000..4c85ebb --- /dev/null +++ b/karmaworld/templates/account/email/email_confirmation_signup_subject.txt @@ -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 index 0000000..3c960da --- /dev/null +++ b/karmaworld/templates/account/email/email_confirmation_subject.txt @@ -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 index 0000000..585e8b3 --- /dev/null +++ b/karmaworld/templates/account/email/password_reset_key_message.txt @@ -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 index 0000000..aa80d11 --- /dev/null +++ b/karmaworld/templates/account/email/password_reset_key_subject.txt @@ -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 index 0000000..9d0d33e --- /dev/null +++ b/karmaworld/templates/account/email_confirm.html @@ -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 %} +

{% trans "Confirm E-mail Address" %}

+ +{% if confirmation %} + +{% user_display confirmation.email_address.user as user_display %} + +

{% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

+ +
+{% csrf_token %} + +
+ +{% else %} + +{% url 'account_email' as email_url %} + +

{% blocktrans %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktrans %}

+ +{% endif %} + +{% endblock %} diff --git a/karmaworld/templates/account/email_confirmed.html b/karmaworld/templates/account/email_confirmed.html new file mode 100644 index 0000000..bd498d3 --- /dev/null +++ b/karmaworld/templates/account/email_confirmed.html @@ -0,0 +1,17 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% load account %} + +{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} + + +{% block content %} + +

{% trans "Confirm E-mail Address" %}

+ +{% user_display email_address.user as user_display %} + +

{% blocktrans with email_address.email as email %}You have confirmed that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

+ +{% endblock %} diff --git a/karmaworld/templates/account/login.html b/karmaworld/templates/account/login.html new file mode 100644 index 0000000..83cf279 --- /dev/null +++ b/karmaworld/templates/account/login.html @@ -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 %} + +

{% trans "Sign In" %}

+ +{% if socialaccount.providers %} +

{% blocktrans with site.name as site_name %}Please sign in with one +of your existing third party accounts. Or, sign up +for a {{site_name}} account and sign in below:{% endblocktrans %}

+ +
+ + + + + +
+ +{% include "socialaccount/snippets/login_extra.html" %} + +{% else %} +

{% blocktrans %}If you have not created an account yet, then please +sign up first.{% endblocktrans %}

+{% endif %} + +
+ {% csrf_token %} + {{ form.as_p }} + {% if redirect_field_value %} + + {% endif %} + {% trans "Forgot Password?" %} + +
+ +{% endblock %} diff --git a/karmaworld/templates/account/logout.html b/karmaworld/templates/account/logout.html new file mode 100644 index 0000000..4447ee8 --- /dev/null +++ b/karmaworld/templates/account/logout.html @@ -0,0 +1,22 @@ +{% extends "account/base.html" %} + +{% load url from future %} +{% load i18n %} + +{% block head_title %}{% trans "Sign Out" %}{% endblock %} + +{% block content %} +

{% trans "Sign Out" %}

+ +

{% trans 'Are you sure you want to sign out?' %}

+ +
+ {% csrf_token %} + {% if redirect_field_value %} + + {% endif %} + +
+ + +{% 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 index 0000000..de55571 --- /dev/null +++ b/karmaworld/templates/account/messages/cannot_delete_primary_email.txt @@ -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 index 0000000..7a526f8 --- /dev/null +++ b/karmaworld/templates/account/messages/email_confirmation_sent.txt @@ -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 index 0000000..3427a4d --- /dev/null +++ b/karmaworld/templates/account/messages/email_confirmed.txt @@ -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 index 0000000..5cf7cf9 --- /dev/null +++ b/karmaworld/templates/account/messages/email_deleted.txt @@ -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 index 0000000..f49248a --- /dev/null +++ b/karmaworld/templates/account/messages/logged_in.txt @@ -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 index 0000000..2cd4627 --- /dev/null +++ b/karmaworld/templates/account/messages/logged_out.txt @@ -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 index 0000000..e01766b --- /dev/null +++ b/karmaworld/templates/account/messages/password_changed.txt @@ -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 index 0000000..e36cef8 --- /dev/null +++ b/karmaworld/templates/account/messages/password_set.txt @@ -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 index 0000000..b6a70dd --- /dev/null +++ b/karmaworld/templates/account/messages/primary_email_set.txt @@ -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 index 0000000..9c9d0d8 --- /dev/null +++ b/karmaworld/templates/account/messages/unverified_primary_email.txt @@ -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 index 0000000..c6daea1 --- /dev/null +++ b/karmaworld/templates/account/password_change.html @@ -0,0 +1,16 @@ +{% extends "account/base.html" %} + +{% load url from future %} +{% load i18n %} + +{% block head_title %}{% trans "Change Password" %}{% endblock %} + +{% block content %} +

{% trans "Change Password" %}

+ +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/karmaworld/templates/account/password_reset.html b/karmaworld/templates/account/password_reset.html new file mode 100644 index 0000000..1616960 --- /dev/null +++ b/karmaworld/templates/account/password_reset.html @@ -0,0 +1,30 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% load account %} + +{% block head_title %}{% trans "Password Reset" %}{% endblock %} + +{% block content %} + +

{% trans "Password Reset" %}

+ {% if user.is_authenticated %} + {% include "account/snippets/already_logged_in.html" %} + {% endif %} + +

{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

+ +
+ {% csrf_token %} + {{ form.as_p }} + +
+ +

{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}

+{% endblock %} + +{% block extra_body %} + +{% endblock %} diff --git a/karmaworld/templates/account/password_reset_done.html b/karmaworld/templates/account/password_reset_done.html new file mode 100644 index 0000000..e90504f --- /dev/null +++ b/karmaworld/templates/account/password_reset_done.html @@ -0,0 +1,16 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% load account %} + +{% block head_title %}{% trans "Password Reset" %}{% endblock %} + +{% block content %} +

{% trans "Password Reset" %}

+ + {% if user.is_authenticated %} + {% include "account/snippets/already_logged_in.html" %} + {% endif %} + +

{% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

+{% 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 index 0000000..e30b5a8 --- /dev/null +++ b/karmaworld/templates/account/password_reset_from_key.html @@ -0,0 +1,24 @@ +{% extends "account/base.html" %} + +{% load url from future %} +{% load i18n %} +{% block head_title %}{% trans "Change Password" %}{% endblock %} + +{% block content %} +

{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}

+ + {% if token_fail %} + {% url 'account_reset_password' as passwd_reset_url %} +

{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}

+ {% else %} + {% if form %} +
+ {% csrf_token %} + {{ form.as_p }} + +
+ {% else %} +

{% trans 'Your password is now changed.' %}

+ {% 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 index 0000000..25608ad --- /dev/null +++ b/karmaworld/templates/account/password_reset_from_key_done.html @@ -0,0 +1,10 @@ +{% extends "account/base.html" %} + +{% load url from future %} +{% load i18n %} +{% block head_title %}{% trans "Change Password" %}{% endblock %} + +{% block content %} +

{% trans "Change Password" %}

+

{% trans 'Your password is now changed.' %}

+{% endblock %} diff --git a/karmaworld/templates/account/password_set.html b/karmaworld/templates/account/password_set.html new file mode 100644 index 0000000..dbb4ef9 --- /dev/null +++ b/karmaworld/templates/account/password_set.html @@ -0,0 +1,16 @@ +{% extends "account/base.html" %} + +{% load url from future %} +{% load i18n %} + +{% block head_title %}{% trans "Set Password" %}{% endblock %} + +{% block content %} +

{% trans "Set Password" %}

+ +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/karmaworld/templates/account/signup.html b/karmaworld/templates/account/signup.html new file mode 100644 index 0000000..71d6956 --- /dev/null +++ b/karmaworld/templates/account/signup.html @@ -0,0 +1,25 @@ +{% extends "account/base.html" %} + +{% load url from future %} +{% load i18n %} + +{% block head_title %}{% trans "Signup" %}{% endblock %} + +{% block content %} +

{% trans "Sign Up" %}

+ +

{% blocktrans %}Already have an account? Then please sign in.{% endblocktrans %}

+ +
+ {% csrf_token %} + {{ form.as_p }} + {% if redirect_field_value %} + + {% endif %} + +
+ + +{% endblock %} + + diff --git a/karmaworld/templates/account/signup_closed.html b/karmaworld/templates/account/signup_closed.html new file mode 100644 index 0000000..5ef0709 --- /dev/null +++ b/karmaworld/templates/account/signup_closed.html @@ -0,0 +1,14 @@ +{% extends "account/base.html" %} + +{% load url from future %} +{% load i18n %} + +{% block head_title %}{% trans "Sign Up Closed" %}{% endblock %} + +{% block content %} +

{% trans "Sign Up Closed" %}

+ +

{% trans "We are sorry, but the sign up is currently closed." %}

+{% 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 index 0000000..00799f0 --- /dev/null +++ b/karmaworld/templates/account/snippets/already_logged_in.html @@ -0,0 +1,5 @@ +{% load i18n %} +{% load account %} + +{% user_display user as user_display %} +

{% trans "Note" %}: {% blocktrans %}you are already logged in as {{ user_display }}.{% endblocktrans %}

diff --git a/karmaworld/templates/account/verification_sent.html b/karmaworld/templates/account/verification_sent.html new file mode 100644 index 0000000..5f71331 --- /dev/null +++ b/karmaworld/templates/account/verification_sent.html @@ -0,0 +1,12 @@ +{% extends "account/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} + +{% block content %} +

{% trans "Verify Your E-mail Address" %}

+ +

{% 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 %}

+ +{% endblock %} diff --git a/karmaworld/templates/account/verified_email_required.html b/karmaworld/templates/account/verified_email_required.html new file mode 100644 index 0000000..19fff58 --- /dev/null +++ b/karmaworld/templates/account/verified_email_required.html @@ -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 %} +

{% trans "Verify Your E-mail Address" %}

+ +{% url 'account_email' as email_url %} + +

{% 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 %}

+ +

{% 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 %}

+ +

{% blocktrans %}Note: you can still change your e-mail address.{% endblocktrans %}

+ + +{% endblock %} diff --git a/karmaworld/templates/base.html b/karmaworld/templates/base.html index 8fb12f8..204824a 100644 --- a/karmaworld/templates/base.html +++ b/karmaworld/templates/base.html @@ -29,34 +29,7 @@ {% endblock %} - - - + {% include 'partial/trackers.html' %} diff --git a/karmaworld/templates/header.html b/karmaworld/templates/header.html index 570e4ab..502cda2 100644 --- a/karmaworld/templates/header.html +++ b/karmaworld/templates/header.html @@ -35,8 +35,13 @@ {% endif %} -
- About +
+ {% if request.user.is_authenticated %} + Log Out + {% else %} + Log In + {% endif %} + About
diff --git a/karmaworld/templates/notes/search_results.html b/karmaworld/templates/notes/search_results.html index 65e408b..a9bd52b 100644 --- a/karmaworld/templates/notes/search_results.html +++ b/karmaworld/templates/notes/search_results.html @@ -62,6 +62,9 @@ {% else %}

Sorry! No results were found.

+ {% if error %} +

There was an error with your search.

+ {% endif %}
{% endif %} diff --git a/karmaworld/templates/partial/filepicker.html b/karmaworld/templates/partial/filepicker.html index 44575ed..d675074 100644 --- a/karmaworld/templates/partial/filepicker.html +++ b/karmaworld/templates/partial/filepicker.html @@ -1,4 +1,5 @@ {% load url from future %} +{% load socialaccount_tags %}
@@ -28,8 +29,13 @@

Thank you for sharing:

-

Your files are being processed and should be viewable within a few minutes.

-

If you'd like to share again, please click here.

+ {% if not request.user.is_authenticated %} +

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.

+

+ +

+ {% endif %} @@ -62,24 +68,9 @@ -
-
-
-
-
- Your Email Address (Optional) - -
-
-
-
-
-
- Save -
-
-
+
+
+ Save
@@ -120,7 +111,7 @@ $(this).parent().parent().remove(); }); - $('#save-btn-wrapper').show(); + $('#save-btn').show(); } $('#save-btn').on('click', function(e){ @@ -154,15 +145,12 @@ $('#uploaded_files').append($('
  • ', {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 index 0000000..1ca2b09 --- /dev/null +++ b/karmaworld/templates/partial/trackers.html @@ -0,0 +1,29 @@ + + + + diff --git a/karmaworld/templates/socialaccount/authentication_error.html b/karmaworld/templates/socialaccount/authentication_error.html new file mode 100644 index 0000000..0300295 --- /dev/null +++ b/karmaworld/templates/socialaccount/authentication_error.html @@ -0,0 +1,11 @@ +{% extends "socialaccount/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Social Network Login Failure" %}{% endblock %} + +{% block content %} +

    {% trans "Social Network Login Failure" %}

    + +

    {% trans "An error occurred while attempting to login via your social network account." %}

    +{% endblock %} diff --git a/karmaworld/templates/socialaccount/base.html b/karmaworld/templates/socialaccount/base.html new file mode 100644 index 0000000..18530d1 --- /dev/null +++ b/karmaworld/templates/socialaccount/base.html @@ -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 index 0000000..def62e1 --- /dev/null +++ b/karmaworld/templates/socialaccount/connections.html @@ -0,0 +1,55 @@ +{% extends "socialaccount/base.html" %} + +{% load i18n %} +{% load url from future %} + +{% block head_title %}{% trans "Account Connections" %}{% endblock %} + +{% block content %} +

    {% trans "Account Connections" %}

    + +{% if form.accounts %} +

    {% blocktrans %}You can sign in to your account using any of the following third party accounts:{% endblocktrans %}

    + + +
    +{% csrf_token %} + +
    +{% if form.non_field_errors %} +
    {{form.non_field_errors}}
    +{% endif %} + +{% for base_account in form.accounts %} +{% with base_account.get_provider_account as account %} +
    + +
    +{% endwith %} +{% endfor %} + +
    + +
    + +
    + +
    + +{% else %} +

    {% trans 'You currently have no social network accounts connected to this account.' %}

    +{% endif %} + +

    {% trans 'Add a 3rd Party Account' %}

    + +
      +{% include "socialaccount/snippets/provider_list.html" with process="connect" %} +
    + +{% 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 index 0000000..f73a769 --- /dev/null +++ b/karmaworld/templates/socialaccount/login_cancelled.html @@ -0,0 +1,17 @@ +{% extends "socialaccount/base.html" %} + +{% load url from future %} +{% load i18n %} + +{% block head_title %}{% trans "Login Cancelled" %}{% endblock %} + +{% block content %} + +

    {% trans "Login Cancelled" %}

    + +{% url 'account_login' as login_url %} + +

    {% blocktrans %}You decided to cancel logging in to our site using one of your existing accounts. If this was a mistake, please proceed to sign in.{% endblocktrans %}

    + +{% endblock %} + diff --git a/karmaworld/templates/socialaccount/messages/account_connected.txt b/karmaworld/templates/socialaccount/messages/account_connected.txt new file mode 100644 index 0000000..be6aa60 --- /dev/null +++ b/karmaworld/templates/socialaccount/messages/account_connected.txt @@ -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 index 0000000..e90f6cc --- /dev/null +++ b/karmaworld/templates/socialaccount/messages/account_connected_other.txt @@ -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 index 0000000..fd43f30 --- /dev/null +++ b/karmaworld/templates/socialaccount/messages/account_disconnected.txt @@ -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 index 0000000..435b86c --- /dev/null +++ b/karmaworld/templates/socialaccount/signup.html @@ -0,0 +1,24 @@ +{% extends "socialaccount/base.html" %} +{% load url from future %} + +{% load i18n %} + +{% block head_title %}{% trans "Signup" %}{% endblock %} + +{% block content %} +

    {% trans "Sign Up" %}

    + +

    {% 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 %}

    + + + + +{% endblock %} diff --git a/karmaworld/templates/socialaccount/snippets/login_extra.html b/karmaworld/templates/socialaccount/snippets/login_extra.html new file mode 100644 index 0000000..f2c5225 --- /dev/null +++ b/karmaworld/templates/socialaccount/snippets/login_extra.html @@ -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 index 0000000..202c5c2 --- /dev/null +++ b/karmaworld/templates/socialaccount/snippets/provider_list.html @@ -0,0 +1,19 @@ +{% load socialaccount %} + +{% for provider in socialaccount.providers %} +{% if provider.id == "openid" %} +{% for brand in provider.get_brands %} +
  • + {{brand.name}} +
  • +{% endfor %} +{% endif %} +
  • + {{provider.name}} +
  • +{% endfor %} + diff --git a/karmaworld/templates/user_profile.html b/karmaworld/templates/user_profile.html new file mode 100644 index 0000000..219a70b --- /dev/null +++ b/karmaworld/templates/user_profile.html @@ -0,0 +1,26 @@ +{% extends "account/base.html" %} +{% load url from future %} +{% load account %} + +{% block head_title %} + Your KarmaNotes Profile +{% endblock %} + +{% block content %} +
    + +
    +
    +

    Hello there, {% user_display user %}.

    +

    Here are the notes that you've uploaded:

    + +
    +
    + + +
    +{% endblock %} diff --git a/karmaworld/urls.py b/karmaworld/urls.py index 7a88d16..1718cfc 100644 --- a/karmaworld/urls.py +++ b/karmaworld/urls.py @@ -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\d+)$', RawNoteDetailView.as_view(), name='note_raw'), #url(r'^pdfview$', PDFView.as_view(), name='pdf'), diff --git a/reqs/common.txt b/reqs/common.txt index 9bdfd57..90fac64 100644 --- a/reqs/common.txt +++ b/reqs/common.txt @@ -20,3 +20,4 @@ python-twitter gdshortener git+https://github.com/flaptor/indextank-py.git html2text +django-allauth