Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

GraphSpace Email Lists #393

Open
wants to merge 31 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e30eed4
Create PULL_REQUEST_TEMPLATE.md
adbharadwaj Mar 4, 2018
52ddf6e
Changes for controllers.py
JingVT Aug 14, 2018
a0bf6a7
Changes in model for email lists features
JingVT Aug 14, 2018
462358e
Update activate account page
JingVT Aug 14, 2018
ff7dafe
Update templates
JingVT Aug 14, 2018
6198a53
Update log in js file
JingVT Aug 14, 2018
052c249
Add mailmanclient to requirements
JingVT Aug 14, 2018
670cd44
Add email lists docs
JingVT Aug 14, 2018
d132866
Add email lists settings
JingVT Aug 14, 2018
4e179b5
Update local.py
JingVT Aug 14, 2018
9cc9572
Update local.py
JingVT Aug 14, 2018
9d7321c
Update views.py
JingVT Aug 14, 2018
499d9cf
Update templates
JingVT Aug 14, 2018
5328d0f
Update views.py
JingVT Aug 14, 2018
382ca36
Update PULL_REQUEST_TEMPLATE.md
JingVT Aug 14, 2018
40aeb5b
Update PULL_REQUEST_TEMPLATE.md
JingVT Aug 14, 2018
d3201f4
Add email lists docs images
JingVT Aug 14, 2018
d921ae7
Change data type
JingVT Nov 27, 2018
5196423
Change data type
JingVT Nov 27, 2018
12e361a
change '1' to 1
JingVT Nov 27, 2018
f5a6d6d
Change '1' to 1
JingVT Nov 27, 2018
3c28afc
Update local.py
JingVT Nov 27, 2018
5909b49
Update local.py
JingVT Nov 27, 2018
43e0f70
Update local.py
JingVT Nov 27, 2018
63d8c6f
Update local.py
JingVT Nov 27, 2018
752c508
Update local.py
JingVT Nov 27, 2018
9a33289
Update local.py
JingVT Nov 27, 2018
45c5b02
Update models.py
JingVT Nov 29, 2018
0f443ec
Update requirements.txt
JingVT Nov 29, 2018
235b064
Update controllers.py
JingVT Nov 29, 2018
43ec81e
Update dal.py
JingVT Nov 29, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## Purpose
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you merge the latest changes to develop branches on Main repo?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to merge validates email address by sending a confirmation email to users, before merging email lists. Do you mind to review pull#238 before I merge it? Thank you.

Built announcements and users email lists to post important announcements and allow users to communicate with each other.

## Approach
Invoke GNU Mailman3 REST API.

#### Open Questions and Pre-Merge TODOs
- [ ] Merge [validates email address by sending a confirmation email to users](https://github.com/Murali-group/GraphSpace/pull/368)
- [ ] [Install and Configure Mailman3 Suite for GraphSpace](https://github.com/Murali-group/GraphSpace/wiki/Install-and-Configure-Mailman3-Suite-for-GraphSpace-(Ubuntu-16.04,-PostgreSQL,-Apache2,-Postfix))
- [ ] Create the email lists of GraphSpace and add them to settings file
- [ ] Update the database.

## Learning
- [Mailman 3 suite documents](http://docs.mailman3.org/en/latest/)

- [Install and Configure Mailman3 Suite for GraphSpace](https://github.com/Murali-group/GraphSpace/wiki/Install-and-Configure-Mailman3-Suite-for-GraphSpace-(Ubuntu-16.04,-PostgreSQL,-Apache2,-Postfix))


#### Blog Posts
- [How to Pull Request](https://github.com/flexyford/pull-request) Github Repo with Learning focused Pull Request Template.
57 changes: 47 additions & 10 deletions applications/home/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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'])),
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:
Expand All @@ -251,21 +254,55 @@ def register(request):
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']
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(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": <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.
Expand All @@ -290,4 +327,4 @@ def images(request, query):
:param request: HTTP GET Request

"""
return redirect('/static' + request.path)
return redirect('/static' + request.path)
51 changes: 44 additions & 7 deletions applications/users/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -30,21 +37,35 @@ 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
if password is not 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)


Expand Down Expand Up @@ -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.
Expand All @@ -146,7 +169,9 @@ def add_user(request, email=None, password="graphspace_public_user", is_admin=0)
"""
email = "public_user_%[email protected]" % 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):
Expand Down Expand Up @@ -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)
Expand All @@ -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)

4 changes: 4 additions & 0 deletions applications/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add documentation about the newly added fields? Since account status is integer what are the possible values and what they mean?

email_confirmation_code = Column(String, nullable=False, default=0)
email_list_announcement = Column(String, nullable=False, default=0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unclear what email_list_announcement field does?

email_list_user = Column(String, nullable=False, default=0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same!


password_reset_codes = relationship("PasswordResetCode", back_populates="user", cascade="all, delete-orphan")
owned_groups = relationship("Group", back_populates="owner", cascade="all, delete-orphan")
Expand Down
39 changes: 39 additions & 0 deletions docs/Email_Lists_for_GraphSpace.md
Original file line number Diff line number Diff line change
@@ -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 [[email protected]](mailto:[email protected]).

### Unsubscribe
To unsubscribe from users list, send an email with 'unsubscribe' in the subject to [[email protected]](mailto:[email protected]).

### Post
To post to users list, send your email to [[email protected]](mailto:[email protected]).

### Archives
You can access the [archives](http://email.graphspace.org/hyperkitty/list/[email protected]/) 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 [[email protected]](mailto:[email protected]).

### Unsubscribe
To unsubscribe from announcements list, send an email with 'unsubscribe' in the subject to [[email protected]](mailto:[email protected]).

### Archives
You can access the [archives](http://email.graphspace.org/hyperkitty/list/[email protected]/) of the announcements list.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 5 additions & 1 deletion graphspace/settings/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
# URL through which to access graphspace
URL_PATH = "http://localhost:8000/"

# Email lists of graphspace
ANNOUNCEMENTS_LIST = '[email protected]'
USERS_LIST = '[email protected]'

# If tracking is enabled for GraphSpace in Google Analytics
GOOGLE_ANALYTICS_PROPERTY_ID = 'UA-00000000-0'

Expand Down Expand Up @@ -45,4 +49,4 @@
'HOST': 'localhost',
'PORT': '5432'
}
}
}
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ elasticsearch-dsl>=5.0.0,<6.0.0
sphinx-rtd-theme
sphinx
recommonmark
mailmanclient
26 changes: 23 additions & 3 deletions static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we storing 1 as a string?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because in models.py I defined the data type of email_list as String. Of course, we can change it to boolean or integer if necessary. '1' means users choose to join the corresponding email_list_anncouncement or email_list_user. '0' means they won't join.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. It should be either Integer or Boolean. I would say Boolean.
  2. What is the difference between email_list_anncouncement and email_list_user?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Ok,I will change to Boolean
  2. GraphSpace administrators post announcements to announcement email list. Users use user email list to post messages and discuss questions.

}

if (user_checked.checked == true) {
email_list_user = '1';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same?

}

if (!$("#user_id") || user_id.length == 0) {
$.notify({
Expand Down Expand Up @@ -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({
Expand All @@ -115,4 +135,4 @@ var header = {
});
});
}
};
};
Loading