Skip to content

Commit

Permalink
Group Backend Routes & Frontend forms
Browse files Browse the repository at this point in the history
  • Loading branch information
Sumukh committed Jan 31, 2016
1 parent b6863f9 commit e725d8a
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 76 deletions.
2 changes: 2 additions & 0 deletions server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ def create_app(object_name):
# register our blueprints
app.register_blueprint(main)

# OAuth should not need CSRF protection
csrf.exempt(auth)
app.register_blueprint(auth)

app.register_blueprint(admin, url_prefix='/admin')
Expand Down
86 changes: 79 additions & 7 deletions server/controllers/student.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
url_for, session, current_app, abort
from flask.ext.login import login_user, logout_user, login_required, \
current_user
from werkzeug.exceptions import BadRequest

import functools

from server.constants import VALID_ROLES, STAFF_ROLES, STUDENT_ROLE
from server.extensions import cache
from server.forms import InviteMemberForm, RemoveMemberForm, DummyForm
from server.forms import CSRFForm
from server.models import User, Course, Assignment, Group, Backup, db

from server.utils import is_safe_redirect_url

student = Blueprint('student', __name__)

Expand Down Expand Up @@ -52,7 +53,7 @@ def index(auto_redir=True):
}
# Make the choice for users in one course
if len(enrollments) == 1 and auto_redir:
return redirect(url_for(".course", cid=enrollments[0].course_id))
return redirect(url_for(".course", course=enrollments[0].course.offering))
return render_template('student/courses/index.html', **courses)


Expand Down Expand Up @@ -96,8 +97,10 @@ def assignment(course, assign):
fs = assign.final_submission(user_ids)
group = Group.lookup(current_user, assign)
can_invite = assign.max_group_size > 1
can_remove = False
if group:
can_invite = len(group.members) < assign.max_group_size
can_remove = current_user in [m.user for m in group.members if m.status == 'active']

data = {
'course': course,
Expand All @@ -108,9 +111,8 @@ def assignment(course, assign):
'flagged' : fs and fs.flagged,
'group' : group,
'can_invite': can_invite,
'invite_form': InviteMemberForm(),
'remove_form': RemoveMemberForm(),
'flag_form': DummyForm() # TODO: Possible to use just one "SingleButton" Form Class
'can_remove': can_remove,
'csrf_form': CSRFForm()
}
return render_template('student/assignment/index.html', **data)

Expand Down Expand Up @@ -182,6 +184,76 @@ def flag(course, assign, bid):
assign.flag(bid, user_ids)
else:
assign.unflag(bid, user_ids)
return redirect(next_url)
if is_safe_redirect_url(request, next_url):
return redirect(next_url)
else:
flash("Not a valid redirect", "danger")
abort(400)
else:
abort(404)

@student.route(ASSIGNMENT_DETAIL + "group/invite", methods=['POST'])
@login_required
@get_course
def group_invite(course, assign):
assignment = Assignment.by_name(assign, course.offering)
if not assignment:
abort(404)

invitee = User.lookup(request.form['email'])
if not invitee:
flash("{} is not enrolled".format(request.form['email']), 'warning')
else:
try:
Group.invite(current_user, invitee, assignment)
except BadRequest as e:
flash(e.description, 'danger')
return redirect(url_for('.assignment', course=course.offering, assign=assign))


@student.route(ASSIGNMENT_DETAIL + "group/remove", methods=['POST'])
@login_required
@get_course
def group_remove(course, assign):
assignment = Assignment.by_name(assign, course.offering)
if not assignment:
abort(404)

target = User.lookup(request.form['email'])
group = Group.lookup(current_user, assignment)
if not target:
flash("{} is not enrolled".format(request.form['email']), 'warning')
elif not group:
flash("You are not in a group", 'warning')
else:
try:
group.remove(current_user, target)
except BadRequest as e:
flash(e.description, 'danger')

return redirect(url_for('.assignment', course=course.offering, assign=assign))

@student.route(ASSIGNMENT_DETAIL + "group/respond", methods=['POST'])
@login_required
@get_course
def group_respond(course, assign):
assignment = Assignment.by_name(assign, course.offering)
if not assignment:
abort(404)

action = request.form.get('action', None)
if not action or action not in ['accept', 'decline']:
abort(400)
group = Group.lookup(current_user, assignment)
if not group:
flash("You are not in a group")
else:
try:
if action == "accept":
group.accept(current_user)
else:
group.decline(current_user)
except BadRequest as e:
flash(e.description, 'warning')

return redirect(url_for('.assignment', course=course.offering, assign=assign))
16 changes: 4 additions & 12 deletions server/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flask_wtf import Form
from wtforms import StringField, DateTimeField, BooleanField, IntegerField
from wtforms import SelectField, TextAreaField, SubmitField, validators
from wtforms import StringField, DateTimeField, BooleanField, IntegerField, \
SelectField, TextAreaField, SubmitField, HiddenField, validators

from wtforms.widgets.core import HTMLString, html_params, escape
from wtforms.fields.html5 import EmailField
Expand Down Expand Up @@ -112,13 +112,5 @@ def validate(self):
return False
return True

class InviteMemberForm(BaseForm):
email = EmailField(u'Email',
validators=[validators.required(), validators.email()])
submit = SubmitField('Invite')

class RemoveMemberForm(BaseForm):
submit = SubmitField('Remove')

class DummyForm(BaseForm):
submit = SubmitField('Submit')
class CSRFForm(BaseForm):
pass
3 changes: 3 additions & 0 deletions server/static/css/student.css
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@ header .logo {
input.button-small {
border:none;
}
button.button-small {
border:none;
}

.button-small {
background-color:#eee;
Expand Down
19 changes: 19 additions & 0 deletions server/static/js/student.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
jQuery(document).ready(function($){
$('.remove-confirm').on('click',function(e){
e.preventDefault();
var form = $(this).parents('form');
var info = form[0][1].value;
swal({
title: "Just double checking",
text: "Are you sure you want to remove " + info + " from the group?",
showCancelButton: true,
confirmButtonText: "Yes, I'm sure!",
closeOnConfirm: true
}, function(isConfirm){
debugger;
if (isConfirm) form.submit();

});
})

});
68 changes: 68 additions & 0 deletions server/templates/student/assignment/group_table.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{% import 'student/assignment/_formhelpers.html' as forms %}

<table class="top-align">
<tbody>
<tr class="header">
<th>Email</th>
<th>Status</th>
<th>Info</th>
</tr>
{% if group %}
{% for member in group.members %}
<tr>
<td><span>{{ member.user.email }}</span></td>
<td><span>{{ member.status }} </span> </td>
<td>
{% if member.status == 'active' and can_remove %}
{% call forms.render_form(csrf_form, action_url='group/remove', class_='') %}
<input class="hidden" id="email" class="form-action" name="email" type="hidden" value="{{ member.user.email }}">
<input class="hidden" class="form-action" id="action" name="action" type="hidden" value="Remove">
{% if member.user == current_user %}
<input class="button-small remove-confirm" type="submit" value="Leave">
{% else %}
<input class="button-small remove-confirm" type="submit" value="Remove">
{% endif %}
{% endcall %}
{% elif member.status == 'pending' %}
{% if member.user == current_user %}
{% call forms.render_form(csrf_form, action_url='group/respond', class_='') %}
<input class="hidden" class="form-info" id="email" name="email" type="hidden" value="{{ member.user.email }}">
<input class="hidden" class="form-action" id="action" name="action" type="hidden" value="accept">
<button class="button-small" type="submit" value="Accept">
<i class="fa fa-check "></i>
</button>
{% endcall %}
{% endif %}
{% call forms.render_form(csrf_form, action_url='group/respond', class_='') %}
<input class="hidden" class="form-user" id="email" name="email" type="hidden" value="{{ member.user.email }}">
<input class="hidden" class="form-action" id="action" name="action" type="hidden" value="decline">
<button class="button-small remove-confirm" type="submit" value="Decline">
<i class="fa fa-minus-circle"></i>
</button>
{% endcall %}
{% endif %}
</td>
</tr>
{% endfor %}
{% elif assignment.active %}
<tr>
<td><span>{{ current_user.email }}</span></td>
<td><span> No Group </span></td>
<td><span> Max {{ assignment.max_group_size }} Members</span></td>
</tr>
{% endif %}
</tbody>
</table>
{% if can_invite %}
<div class="row">
{% call forms.render_form(csrf_form, action_url='group/invite', class_='form') %}
<div class="col-md-9 col-xs-8">
<input class="invite-input" required id="email" name="email" placeholder="test@example.com" type="email" value="">
</div>
<div class="col-md-3 col-xs-4">
<input class="button-small btn-invite" id="submit" name="submit" type="submit" value="Invite">
</div>
{% endcall %}
</div>

{% endif %}
67 changes: 11 additions & 56 deletions server/templates/student/assignment/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
</div>
<h1>{{ assignment.display_name}}</h1>
</div>

{% include 'alerts.html' %}

</div>
Expand All @@ -53,55 +52,12 @@ <h1>{{ assignment.display_name}}</h1>
<div class="col-md-6 col-sm-6">
<h2> Group </h2>

<table class="top-align">
<tbody>
<tr class="header">
<th>Email</th>
<th>Info</th>
</tr>
{% if group %}
{% for member in group.members %}
<tr>
<td><span>{{ member.user.email }}</span></td>
<td>
{% call forms.render_form(remove_form, action_url='', class_='') %}
{{ forms.render_field(remove_form.submit, label_visible=False, class="button-small") }}
{% endcall %}
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td><span>{{ current_user.email }}</span></td>
<td><span> No Group </span></td>
</tr>
{% endif %}

</tbody>
</table>
{% if can_invite %}
<div class="row">
<!-- TODO Invite Form URL -->
{% call forms.render_form(invite_form, action_url='', class_='form') %}
<div class="col-md-9 col-xs-8">
{{ forms.render_field(invite_form.email, label_visible=False, class="invite-input", placeholder="test@example.com") }}
</div>
<div class="col-md-3 col-xs-4">
{{ forms.render_field(invite_form.submit, label_visible=False, class="button-small btn-invite") }}
</div>
{% endcall %}
</div>

{% endif %}


{% include 'student/assignment/group_table.html' %}

</div>
<div class="col-md-6 col-sm-6">
<h2> Current Submission </h2>

{% if final_submission %}

<div class="cell col-xs-12">
<div class="cell-title">
<a href="{{ url_for('.code', course=course.offering, assign=assignment.offering_name(), bid=final_submission.id, submit=final_submission.submit)}}" class="due-text green">
Expand All @@ -125,26 +81,25 @@ <h2> Current Submission </h2>
</a>
</span>
</div>
{% else %}

<div class="cell col-xs-12">
<div class="cell-title">
<a class="due-text red"> No Submission </a>
</div>
<div class="cell-text">
<span> {{ current_user.email }} does not have any submissions for {{ assignment.display_name }} </span>
{% else %}
<div class="cell col-xs-12">
<div class="cell-title">
<a class="due-text red"> No Submission </a>
</div>
<div class="cell-text">
<span> {{ current_user.email }} does not have any submissions for {{ assignment.display_name }} </span>
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- start desktop/tablet version -->
<div class="subcontent row hidden-xs hidden-sm">
<div class="col-md-12">
<!-- TODO: Better link to view all backups -->

{{ table.render_subms(subms, course, flagged, flag_form, tname="Recent Submissions") }}
{{ table.render_subms(subms, course, flagged, csrf_form, tname="Recent Submissions") }}
{% if subms %}
<a href="{{ url_for(".list_backups", course=course.offering, assign=assignment.offering_name(), submit=True) }}" class="no-dash button-large">
View More Submissions
Expand Down
2 changes: 1 addition & 1 deletion server/templates/student/course/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
</div>
</div>
</header>
{% include 'alerts.html' %}

<div class="subcontent heading">
<div class="wrap">
Expand All @@ -41,7 +42,6 @@
<h1>{{ course.display_name}}</h1>
</div>

{% include 'alerts.html' %}

</div>
<!-- start desktop/tablet version -->
Expand Down
7 changes: 7 additions & 0 deletions server/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from hashids import Hashids
from werkzeug.routing import BaseConverter, ValidationError
from urllib.parse import urlparse, urljoin

from server.extensions import cache

Expand All @@ -17,3 +18,9 @@ def to_python(self, value):

def to_url(self, value):
return self.hashids.encode(value)

def is_safe_redirect_url(request, target):
host_url = urlparse(request.host_url)
redirect_url = urlparse(urljoin(request.host_url, target))
return redirect_url.scheme in ('http', 'https') and \
host_url.netloc == redirect_url.netloc

0 comments on commit e725d8a

Please sign in to comment.