Skip to content
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ WSGIScriptAlias / /path_to_GraphSpace/graphspace/wsgi.py
Refer to https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/modwsgi/ if any problems occur with the setup.


Documentation
=================

GraphSpace has extensive documentation on the [user interface](http://docs.graphspace.org/en/latest/Quick_Tour_of_GraphSpace.html#welcome-screen), the [REST API](http://docs.graphspace.org/en/latest/Programmers_Guide.html#graphspace-rest-api) and a [Python package for programmatic interaction](http://manual.graphspace.org/projects/graphspace-python/en/latest/tutorial/index.html).


Contributing
=================

Expand Down
2 changes: 2 additions & 0 deletions applications/home/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@
url(r'^about_us/$', views.about_us_page, name='about_us'),
url(r'^forgot_password/$', views.forgot_password_page, name='forgot_password'),
url(r'^reset_password/$', views.reset_password_page, name='reset_password'),
url(r'^activate_account/$', views.activate_account_page, name='activate_account'),

]
39 changes: 32 additions & 7 deletions applications/home/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.template import RequestContext
from graphspace.utils import *
from graphspace.exceptions import *
from graphspace.utils import generate_uid


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 @@ -252,20 +255,42 @@ def register(request):
# RegisterForm is bound to POST data
register_form = RegisterForm(request_body)
if register_form.is_valid():
token = generate_uid()
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
password=register_form.cleaned_data['password'], user_account_status=0, email_confirmation_code=token)

return HttpResponse(json.dumps(json_success_response(200, message='Registered!')),
content_type="application/json")
users.send_confirmation_email(request, request_body['user_id'], token)
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>}
"""

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)
if user is not None:
request.session['uid'] = user.email
request.session['admin'] = user.is_admin
return HttpResponseRedirect('/')

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 Down
28 changes: 22 additions & 6 deletions applications/users/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,23 @@ 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
}
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):
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

return db.update_user(request.db_session, id=user_id, updated_user=user)

Expand Down Expand Up @@ -126,14 +129,15 @@ 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):
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)


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):
"""
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 +150,8 @@ 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)


def is_member_of_group(request, username, group_id):
Expand Down Expand Up @@ -290,6 +295,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 +320,11 @@ 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):
# 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)
14 changes: 12 additions & 2 deletions applications/users/dal.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


@with_session
def add_user(db_session, email, password, is_admin):
def add_user(db_session, email, password, is_admin, user_account_status, email_confirmation_code):
"""
Add a new user.

Expand All @@ -22,7 +22,8 @@ def add_user(db_session, email, password, is_admin):
:param admin: 1 if user has admin access else 0.
:return: User
"""
user = User(email=email, password=password, is_admin = is_admin)
user = User(email=email, password=password, is_admin = is_admin,
user_account_status=user_account_status, email_confirmation_code=email_confirmation_code)
db_session.add(user)
return user

Expand Down Expand Up @@ -113,6 +114,15 @@ def get_password_reset_by_code(db_session, code):
"""
return db_session.query(PasswordResetCode).filter(PasswordResetCode.code == code).one_or_none()

@with_session
def get_email_confirmation_code(db_session, code):
"""
:param db_session: Database session.
:param code: PasswordReset code
:return: PasswordReset if email exists else None
"""
return db_session.query(User).filter(User.email_confirmation_code == code).one_or_none()


@with_session
def update_password_reset(db_session, id, updated_password_reset):
Expand Down
2 changes: 2 additions & 0 deletions applications/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ 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)

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
1 change: 1 addition & 0 deletions graphspace/exceptions/error_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Validation(object):
UserPasswordMisMatch = (1003, "User/Password not recognized")
UserNotAuthorized = (1004, "You are not authorized to access this resource, create an account and contact resource's owner for permission to access this resource.")
UserNotAuthenticated = (1005, "User authentication failed")
UserUnVerified = (10015, "User not verified")

# Graphs API
IsPublicNotSet = (1006, "`is_public` is required to be set to True when `owner_email` and `member_email` are not provided.")
Expand Down
52 changes: 48 additions & 4 deletions static/js/groups_page.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,22 @@ var groupsPage = {
submit: function (e) {
e.preventDefault();

$('#CreateGroupBtn').attr('disabled', true);

var group = {
"name": $("#GroupNameInput").val() == "" ? undefined : $("#GroupNameInput").val(),
"description": $("#GroupDescriptionInput").val() == "" ? undefined : $("#GroupDescriptionInput").val(),
"owner_email": $('#UserEmail').val()
};

if (!group['name']) {
return alert("Please enter in a valid group name!");
$('#CreateGroupBtn').attr('disabled', false);
$.notify({
message: 'Please enter in a valid group name!'
}, {
type: 'warning'
});
return;
}

apis.groups.add(group,
Expand All @@ -89,7 +97,21 @@ var groupsPage = {
},
errorCallback = function (xhr, status, errorThrown) {
// This method is called when error occurs while adding group.
alert(xhr.responseText);
if(xhr.responseJSON.error_message.includes('duplicate key')) {
$.notify({
message: 'Group name ' + group['name'] + ' already exists!'
}, {
type: 'danger'
});
}
else {
$.notify({
message: xhr.responseText
}, {
type: 'danger'
});
}
$('#CreateGroupBtn').attr('disabled', false);
});
}
},
Expand Down Expand Up @@ -282,13 +304,21 @@ var groupPage = {
submit: function (e) {
e.preventDefault();

$('#UpdateGroupBtn').attr('disabled', true);

var group = {
"name": $("#GroupNameInput").val() == "" ? undefined : $("#GroupNameInput").val(),
"description": $("#GroupDescriptionInput").val() == "" ? undefined : $("#GroupDescriptionInput").val()
};

if (!group['name']) {
return alert("Please enter in a valid group name!");
$('#UpdateGroupBtn').attr('disabled', false);
$.notify({
message: 'Please enter in a valid group name!'
}, {
type: 'warning'
});
return
}

apis.groups.update($('#GroupID').val(), group,
Expand All @@ -298,7 +328,21 @@ var groupPage = {
},
errorCallback = function (xhr, status, errorThrown) {
// This method is called when error occurs while updating group.
alert(xhr.responseText);
if(xhr.responseJSON.error_message.includes('duplicate key')) {
$.notify({
message: 'Group name ' + group['name'] + ' already exists!'
}, {
type: 'danger'
});
}
else {
$.notify({
message: xhr.responseText
}, {
type: 'danger'
});
}
$('#UpdateGroupBtn').attr('disabled', false);
});
}
},
Expand Down
7 changes: 6 additions & 1 deletion static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,12 @@ var header = {
"password": password
},
successCallback = function (response) {
window.location.reload();
$('#signupModal').modal('hide');
$.notify({
message: response.Message
}, {
type: 'success'
});
},
errorCallback = function (response) {
$.notify({
Expand Down
17 changes: 17 additions & 0 deletions templates/graph/graph_details_tab.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@
{% endfor %}

</table>
<table class="table table-bordered">
<tr>
<th>Shared with groups</th>
</tr>

{% if shared_groups %}
{% for k in shared_groups %}
<tr>
<td><a href="{% url 'group' group_id=k.id %}">{{k.name|safe}}</a></td>
</tr>
{% endfor %}
{% else %}
<tr>
<td>'The graph is not shared with any groups'</td>
</tr>
{% endif %}
</table>
</div>
</div>
</div>
Expand Down