From e30eed4fbc053e4f79918c040db15c91282e9f9e Mon Sep 17 00:00:00 2001 From: Aditya Bharadwaj Date: Sat, 3 Mar 2018 20:36:51 -0500 Subject: [PATCH 01/31] Create PULL_REQUEST_TEMPLATE.md Adding a pull request template to standardize the description of the proposed changes from contributors. Project contributors will automatically see the template's contents in the pull request body. More details can be found [here](https://help.github.com/articles/creating-a-pull-request-template-for-your-repository/). --- PULL_REQUEST_TEMPLATE.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 PULL_REQUEST_TEMPLATE.md diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..9de17e17 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,20 @@ +## Purpose +_Describe the problem or feature in addition to a link to the issues._ + +Example: +Fixes # . + +## Approach +_How does this change address the problem?_ + +#### Open Questions and Pre-Merge TODOs +- [ ] Use github checklists. When solved, check the box and explain the answer. + +## Learning +_Describe the research stage_ + +_Links to blog posts, patterns, libraries or addons used to solve this problem_ + +#### Blog Posts +- [How to Pull Request](https://github.com/flexyford/pull-request) Github Repo with Learning focused Pull Request Template. + From 52ddf6e08de3e99d35b67f532d22b4d886967b47 Mon Sep 17 00:00:00 2001 From: jing Date: Tue, 14 Aug 2018 08:09:06 -0400 Subject: [PATCH 02/31] Changes for controllers.py --- applications/users/controllers.py | 51 ++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/applications/users/controllers.py b/applications/users/controllers.py index be2fe623..95b9c8da 100644 --- a/applications/users/controllers.py +++ b/applications/users/controllers.py @@ -8,6 +8,13 @@ from graphspace.exceptions import BadRequest, ErrorCodes from graphspace.utils import generate_uid +# The mailmanclient library provides official Python bindings for the GNU Mailman 3 REST API. +from mailmanclient import Client + +# In order to talk to Mailman, the engine's REST server must be running. +# Begin by instantiating a client object to access the root of the REST hierarchy, providing it the base URL, user name and password (for Basic Auth) +client = Client('http://localhost:8001/3.1', 'restadmin', 'restpass') + # import the logging library import logging @@ -30,13 +37,16 @@ def authenticate_user(request, username=None, password=None): 'id': user.id, 'user_id': user.email, 'password': user.password, - 'admin': user.is_admin + 'admin': user.is_admin, + 'user_account_status':user.user_account_status, + 'email_list_announcement': user.email_list_announcement, + 'email_list_user': user.email_list_user } else: return None -def update_user(request, user_id, email=None, password=None, is_admin=None): +def update_user(request, user_id, email=None, password=None, is_admin=None, user_account_status=None, email_list_announcement=None, email_list_user=None): user = {} if email is not None: user['email'] = email @@ -44,7 +54,18 @@ def update_user(request, user_id, email=None, password=None, is_admin=None): user['password'] = bcrypt.hashpw(password, bcrypt.gensalt()) if is_admin is not None: user['is_admin'] = is_admin - + if user_account_status is not None: + user['user_account_status'] = user_account_status + if email_list_announcement is not None: + user['email_list_announcement'] = email_list_announcement + if email_list_announcement == '1': + client_list_announcement = client.get_list(settings.ANNOUNCEMENTS_LIST) + client_list_announcement.subscribe(email, pre_verified=True, pre_confirmed=True) + if email_list_user is not None: + user['email_list_user'] = email_list_user + if email_list_user == '1': + client_list_user = client.get_list(settings.USERS_LIST) + client_list_user.subscribe(email, pre_verified=True, pre_confirmed=True) return db.update_user(request.db_session, id=user_id, updated_user=user) @@ -126,14 +147,16 @@ def search_users(request, email=None, limit=20, offset=0, order='desc', sort='na return total, users -def register(request, username=None, password=None): +def register(request, username=None, password=None, user_account_status=None, email_confirmation_code=None, email_list_announcement=None, email_list_user=None): if db.get_user(request.db_session, username): raise BadRequest(request, error_code=ErrorCodes.Validation.UserAlreadyExists, args=username) - return add_user(request, email=username, password=password) + return add_user(request, email=username, password=password, user_account_status=user_account_status, + email_confirmation_code=email_confirmation_code, email_list_announcement=email_list_announcement, + email_list_user=email_list_user) -def add_user(request, email=None, password="graphspace_public_user", is_admin=0): +def add_user(request, email=None, password="graphspace_public_user", is_admin=0, user_account_status=None, email_confirmation_code=None, email_list_announcement=None, email_list_user=None): """ Add a new user. If email and password is not passed, it will create a user with default values. By default a user has no admin access. @@ -146,7 +169,9 @@ def add_user(request, email=None, password="graphspace_public_user", is_admin=0) """ email = "public_user_%s@graphspace.com" % generate_uid(size=10) if email is None else email - return db.add_user(request.db_session, email=email, password=bcrypt.hashpw(password, bcrypt.gensalt()), is_admin=is_admin) + return db.add_user(request.db_session, email=email, password=bcrypt.hashpw(password, bcrypt.gensalt()), is_admin=is_admin, + user_account_status=user_account_status, email_confirmation_code=email_confirmation_code, email_list_announcement=email_list_announcement, + email_list_user=email_list_user) def is_member_of_group(request, username, group_id): @@ -290,6 +315,9 @@ def delete_group_graph(request, group_id, graph_id): def get_password_reset_by_code(request, code): return db.get_password_reset_by_code(request.db_session, code) +def get_email_confirmation_code(request, code): + return db.get_email_confirmation_code(request.db_session, code) + def delete_password_reset_code(request, id): return db.delete_password_reset(request.db_session, id) @@ -312,3 +340,12 @@ def send_password_reset_email(request, password_reset_code): email_from = "GraphSpace Admin" return send_mail(mail_title, message, email_from, [password_reset_code.email], fail_silently=False) + +def send_confirmation_email(request, email, token, email_list_announcement, email_list_user): + # Construct email message + mail_title = 'Activate your account for GraphSpace!' + message = 'Please confirm your email address to complete the registration ' + settings.URL_PATH + 'activate_account/?activation_code=' + token + email_from = "GraphSpace Admin" + + return send_mail(mail_title, message, email_from, [email], fail_silently=False) + From a0bf6a76591b3d2310bc8fc26e38fefb304d46ed Mon Sep 17 00:00:00 2001 From: jing Date: Tue, 14 Aug 2018 08:25:34 -0400 Subject: [PATCH 03/31] Changes in model for email lists features --- applications/users/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/applications/users/models.py b/applications/users/models.py index 053fc6f1..9f3fff00 100644 --- a/applications/users/models.py +++ b/applications/users/models.py @@ -23,6 +23,10 @@ class User(IDMixin, TimeStampMixin, Base): email = Column(String, nullable=False, unique=True, index=True) password = Column(String, nullable=False) is_admin = Column(Integer, nullable=False, default=0) + user_account_status = Column(Integer, nullable=False, default=0) + email_confirmation_code = Column(String, nullable=False, default=0) + email_list_announcement = Column(String, nullable=False, default=0) + email_list_user = Column(String, nullable=False, default=0) password_reset_codes = relationship("PasswordResetCode", back_populates="user", cascade="all, delete-orphan") owned_groups = relationship("Group", back_populates="owner", cascade="all, delete-orphan") From 462358e7ac6caff462818a4428bb9c1ba8b97860 Mon Sep 17 00:00:00 2001 From: jing Date: Tue, 14 Aug 2018 08:32:47 -0400 Subject: [PATCH 04/31] Update activate account page --- applications/home/views.py | 64 +++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/applications/home/views.py b/applications/home/views.py index d9ddf1a0..ac1a3672 100644 --- a/applications/home/views.py +++ b/applications/home/views.py @@ -6,7 +6,8 @@ from django.template import RequestContext from graphspace.utils import * from graphspace.exceptions import * - +from graphspace.utils import generate_uid +from django.conf import settings def home_page(request): """ @@ -26,7 +27,7 @@ def home_page(request): Raises ------ MethodNotAllowed: If a user tries to send requests other than GET i.e., POST, PUT or UPDATE. - +: Notes ------ @@ -225,12 +226,14 @@ def login(request): request_body = json.loads(request.body) user = users.authenticate_user(request, username=request_body['user_id'], password=request_body['pw']) - if user is not None: + if user is not None and user['user_account_status'] == 1: request.session['uid'] = user['user_id'] request.session['admin'] = user['admin'] return HttpResponse( - json.dumps(json_success_response(200, message='%s, Welcome to GraphSpace!' % user['user_id'])), + json.dumps(json_success_response(200, message='%s, welcome to GraphSpace!' % user['user_id'])), content_type="application/json") + elif user is not None and user['user_account_status'] is not 1: + raise ValidationError(request, ErrorCodes.Validation.UserUnVerified) else: raise ValidationError(request, ErrorCodes.Validation.UserPasswordMisMatch) else: @@ -248,24 +251,61 @@ def register(request): if 'POST' == request.method: request_body = json.loads(request.body) + #test_form = request_body if 'user_id' in request_body and 'password' in request_body: # RegisterForm is bound to POST data register_form = RegisterForm(request_body) + if register_form.is_valid(): + token = generate_uid() + email_list_announcement = request_body['email_list_announcement'] + email_list_user = request_body['email_list_user'] + #test_form = email_list_user user = users.register(request, username=register_form.cleaned_data['user_id'], - password=register_form.cleaned_data['password']) - if user is not None: - request.session['uid'] = user.email - request.session['admin'] = user.is_admin - - return HttpResponse(json.dumps(json_success_response(200, message='Registered!')), - content_type="application/json") + password=register_form.cleaned_data['password'], user_account_status=0, email_confirmation_code=token, + email_list_announcement=email_list_announcement, email_list_user=email_list_user) + + users.send_confirmation_email(request, request_body['user_id'], token, email_list_announcement, email_list_user) + #return HttpResponse('register click link', content_type="application/json") #pop green undefined + return HttpResponse(json.dumps(json_success_response(200, message='A verification link has been sent to your email account. '+ + 'Please click on the link to verify your email and continue '+ + 'the registration process.')), + content_type="application/json") else: raise BadRequest(request) else: raise MethodNotAllowed(request) # Handle other type of request methods like GET, PUT, UPDATE. +def activate_account_page(request): + """ + Activate a user account + + :param request: HTTP GET Request containing: + + {"activation_code": } + """ + + context = RequestContext(request) # Checkout base.py file to see what context processors are being applied here. + + if 'GET' == request.method: + user = users.get_email_confirmation_code(request, request.GET.get('activation_code', None)) + users.update_user(request, user.id, user_account_status=1, email=user.email, email_list_announcement=user.email_list_announcement, email_list_user=user.email_list_user) + if user is not None: + request.session['uid'] = user.email + request.session['admin'] = user.is_admin + request.session['email_list_announcement'] = user.email_list_announcement + request.session['email_list_user'] = user.email_list_user + announcement_list_message = 'announcements email list for GraphSpace ' + '(' + settings.ANNOUNCEMENTS_LIST + ')' if user.email_list_announcement == '1' else '' + user_list_message = 'users email list for GraphSpace ' + '(' + settings.USERS_LIST + ')' if user.email_list_user == '1' else '' + comma = ', ' if user.email_list_announcement == '1' and user.email_list_user == '1' else '' + context["success_message"] = 'Thank you! Your account has been activated successfully. You will also receive email confirmation(s) for the following email list(s): ' + announcement_list_message + comma + user_list_message +'.' + + return render(request, 'home/index.html', context) + else: + raise MethodNotAllowed(request) # Handle other type of request methods like GET, PUT, UPDATE. + + def logout(request): """ Log the user out and display logout page. @@ -290,4 +330,4 @@ def images(request, query): :param request: HTTP GET Request """ - return redirect('/static' + request.path) \ No newline at end of file + return redirect('/static' + request.path) From ff7dafec98b8a800839e3e5e8597151cb831ec85 Mon Sep 17 00:00:00 2001 From: jing Date: Tue, 14 Aug 2018 08:38:54 -0400 Subject: [PATCH 05/31] Update templates --- templates/home/index.html | 26 +++++++++++++++++++++++--- templates/partials/signup_modal.html | 13 +++++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/templates/home/index.html b/templates/home/index.html index 31f07b67..75b9dc3c 100644 --- a/templates/home/index.html +++ b/templates/home/index.html @@ -1,6 +1,26 @@ {% extends 'base.html' %} {% load staticfiles %} {% block content %} +{% if success_message != None %} + +{% endif %} +
@@ -10,6 +30,7 @@ From 6198a53b79dbd9a75c9351950173e97ddfcad411 Mon Sep 17 00:00:00 2001 From: jing Date: Tue, 14 Aug 2018 08:48:25 -0400 Subject: [PATCH 06/31] Update log in js file --- static/js/main.js | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/static/js/main.js b/static/js/main.js index aefeaf82..a1fb939e 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -61,6 +61,19 @@ var header = { var user_id = $("#user_id").val(); var password = $("#password").val(); var verify_password = $("#verify_password").val(); + var email_list_announcement = '0'; + var email_list_user = '0'; + var announcement_checked = document.getElementById("email_list_announcement"); + var user_checked = document.getElementById("email_list_user"); + + + if (announcement_checked.checked == true) { + email_list_announcement = '1'; + } + + if (user_checked.checked == true) { + email_list_user = '1'; + } if (!$("#user_id") || user_id.length == 0) { $.notify({ @@ -102,10 +115,17 @@ var header = { //POST Request to log in user jsonRequest('POST', "/register/", { "user_id": user_id, - "password": password + "password": password, + "email_list_announcement": email_list_announcement, + "email_list_user": email_list_user }, successCallback = function (response) { - window.location.reload(); + $('#signupModal').modal('hide'); + $.notify({ + message: response.Message + }, { + type: 'success' + }); }, errorCallback = function (response) { $.notify({ @@ -115,4 +135,4 @@ var header = { }); }); } -}; \ No newline at end of file +}; From 052c249f9dec023dfcd0dc48ec1e0ee5bca290ef Mon Sep 17 00:00:00 2001 From: jing Date: Tue, 14 Aug 2018 08:50:13 -0400 Subject: [PATCH 07/31] Add mailmanclient to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index b75a2132..24be9cff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,4 @@ elasticsearch-dsl>=5.0.0,<6.0.0 sphinx-rtd-theme sphinx recommonmark +mailmanclient From 670cd4452c4a05196f3f4ce40ebf053b5608afc4 Mon Sep 17 00:00:00 2001 From: jing Date: Tue, 14 Aug 2018 08:52:02 -0400 Subject: [PATCH 08/31] Add email lists docs --- docs/Email_Lists_for_GraphSpace.md | 39 ++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 40 insertions(+) create mode 100644 docs/Email_Lists_for_GraphSpace.md diff --git a/docs/Email_Lists_for_GraphSpace.md b/docs/Email_Lists_for_GraphSpace.md new file mode 100644 index 00000000..7bbbf58e --- /dev/null +++ b/docs/Email_Lists_for_GraphSpace.md @@ -0,0 +1,39 @@ + +# Email Lists for GraphSpace + +[GraphSpace](http://graphspace.org) built announcements and users email lists to post important announcements and allow users to communicate with each other. + +## Users Email List for GraphSpace +The users list is meant for all users of GraphSpace to communicate with each other and with the GraphSpace administrators. Please use this list to pose questions about GraphSpace, discuss any problems you may have, give us feedback, request features etc. + +### Subscribe +To subscribe you can choose "Please add me to the users email list for GraphSpace" when create account, + +![Users Email List for GraphSpace](_static/images/email-list/gs-Screenshot-register-users-email-list.png.png) + +or send an email with 'subscribe' in the subject to [graphspace-users-join@graphspace.org](mailto:graphspace-users-join@graphspace.org). + +### Unsubscribe +To unsubscribe from users list, send an email with 'unsubscribe' in the subject to [graphspace-users-leave@graphspace.org](mailto:graphspace-users-leave@graphspace.org). + +### Post +To post to users list, send your email to [graphspace-users@graphspace.org](mailto:graphspace-users@graphspace.org). + +### Archives +You can access the [archives](http://email.graphspace.org/hyperkitty/list/graphspace-users@graphspace.org/) of the users list. + +## Announcements Email List for GraphSpace +The GraphSpace administrators will use this list to post important announcements about GraphSpace. Note that you will not be able to post to this mailing list. If you have a question about GraphSpace, please join the graphspace-users mailing list and post there. + +### Subscribe +To subscribe you can choose "Please add me to the announcements email list for GraphSpace" when create account, + +![Announcements Email List for GraphSpace](_static/images/email-list/gs-Screenshot-register-announcements-email-list.png) + +or send an email with 'subscribe' in the subject to [graphspace-announcements-join@graphspace.org](mailto:graphspace-announcements-join@graphspace.org). + +### Unsubscribe +To unsubscribe from announcements list, send an email with 'unsubscribe' in the subject to [graphspace-announcements-leave@graphspace.org](mailto:graphspace-announcements-leave@graphspace.org). + +### Archives +You can access the [archives](http://email.graphspace.org/hyperkitty/list/graphspace-announcements@graphspace.org/) of the announcements list. diff --git a/docs/index.rst b/docs/index.rst index 3d08fe4f..f661d7cb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,5 +23,6 @@ GraphSpace 2.0 User Manual Sharing_Graphs Editing_Layouts Organizing_Graphs_Using_Tags + Email_Lists_for_GraphSpace Programmers_Guide Release_Notes From d1328668b697cf5eff0117678f253b5b4ddbbc4b Mon Sep 17 00:00:00 2001 From: jing Date: Tue, 14 Aug 2018 09:25:57 -0400 Subject: [PATCH 09/31] Add email lists settings --- graphspace/settings/local.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/graphspace/settings/local.py b/graphspace/settings/local.py index 9b2fa36d..93e20953 100644 --- a/graphspace/settings/local.py +++ b/graphspace/settings/local.py @@ -12,6 +12,10 @@ # URL through which to access graphspace URL_PATH = "http://localhost:8000/" +# Email lists of graphspace +ANNOUNCEMENTS_LIST = 'graphspace-announcements@graphspace.org' +USERS_LIST = 'graphspace-users@graphspace.org' + # If tracking is enabled for GraphSpace in Google Analytics GOOGLE_ANALYTICS_PROPERTY_ID = 'UA-00000000-0' @@ -39,10 +43,10 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'test_database', - 'USER': 'adb', - 'PASSWORD': '', + 'NAME': 'postgres', + 'USER': 'postgres', + 'PASSWORD': 'Cjing_1185426', 'HOST': 'localhost', 'PORT': '5432' } -} \ No newline at end of file +} From 4e179b52dc3695abffc089b0bd488df6f550e7ad Mon Sep 17 00:00:00 2001 From: JingVT <33851894+JingVT@users.noreply.github.com> Date: Tue, 14 Aug 2018 09:30:52 -0400 Subject: [PATCH 10/31] Update local.py --- graphspace/settings/local.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/graphspace/settings/local.py b/graphspace/settings/local.py index 93e20953..9f7bfff9 100644 --- a/graphspace/settings/local.py +++ b/graphspace/settings/local.py @@ -39,13 +39,12 @@ # http://stackoverflow.com/questions/4642011/test-sending-email-without-email-server EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' - DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'postgres', - 'USER': 'postgres', - 'PASSWORD': 'Cjing_1185426', + 'NAME': 'test_database', + 'USER': 'adb', + 'PASSWORD': '', 'HOST': 'localhost', 'PORT': '5432' } From 9cc957282d450150700c141372ae1620275548fe Mon Sep 17 00:00:00 2001 From: JingVT <33851894+JingVT@users.noreply.github.com> Date: Tue, 14 Aug 2018 09:33:07 -0400 Subject: [PATCH 11/31] Update local.py --- graphspace/settings/local.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphspace/settings/local.py b/graphspace/settings/local.py index 9f7bfff9..0cd8e34f 100644 --- a/graphspace/settings/local.py +++ b/graphspace/settings/local.py @@ -39,6 +39,7 @@ # http://stackoverflow.com/questions/4642011/test-sending-email-without-email-server EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', From 9d7321c0e4e023fe09d6f04436eea84290cdcf22 Mon Sep 17 00:00:00 2001 From: JingVT <33851894+JingVT@users.noreply.github.com> Date: Tue, 14 Aug 2018 09:52:10 -0400 Subject: [PATCH 12/31] Update views.py --- applications/home/views.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/applications/home/views.py b/applications/home/views.py index ac1a3672..e343839b 100644 --- a/applications/home/views.py +++ b/applications/home/views.py @@ -27,7 +27,7 @@ def home_page(request): Raises ------ MethodNotAllowed: If a user tries to send requests other than GET i.e., POST, PUT or UPDATE. -: + Notes ------ @@ -251,7 +251,6 @@ def register(request): if 'POST' == request.method: request_body = json.loads(request.body) - #test_form = request_body if 'user_id' in request_body and 'password' in request_body: # RegisterForm is bound to POST data register_form = RegisterForm(request_body) @@ -260,13 +259,11 @@ def register(request): token = generate_uid() email_list_announcement = request_body['email_list_announcement'] email_list_user = request_body['email_list_user'] - #test_form = email_list_user user = users.register(request, username=register_form.cleaned_data['user_id'], password=register_form.cleaned_data['password'], user_account_status=0, email_confirmation_code=token, email_list_announcement=email_list_announcement, email_list_user=email_list_user) users.send_confirmation_email(request, request_body['user_id'], token, email_list_announcement, email_list_user) - #return HttpResponse('register click link', content_type="application/json") #pop green undefined return HttpResponse(json.dumps(json_success_response(200, message='A verification link has been sent to your email account. '+ 'Please click on the link to verify your email and continue '+ 'the registration process.')), From 499d9cf51166f7dc5b9157e380177dbd86c60d94 Mon Sep 17 00:00:00 2001 From: JingVT <33851894+JingVT@users.noreply.github.com> Date: Tue, 14 Aug 2018 09:53:37 -0400 Subject: [PATCH 13/31] Update templates --- templates/partials/signup_modal.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/partials/signup_modal.html b/templates/partials/signup_modal.html index a0434e87..6b3b4c24 100644 --- a/templates/partials/signup_modal.html +++ b/templates/partials/signup_modal.html @@ -7,7 +7,7 @@