Skip to content
This repository has been archived by the owner on Jun 18, 2020. It is now read-only.

Commit

Permalink
Merge branch 'feature/enroll' into develop
Browse files Browse the repository at this point in the history
- Allow direct assignment of members to events, no need for lessons, etc.
- Allow self-enrollment of students, probably secured with a shared password
  • Loading branch information
moschlar committed Oct 30, 2013
2 parents 2942019 + 39d0cec commit f00ce1a
Show file tree
Hide file tree
Showing 15 changed files with 513 additions and 43 deletions.
43 changes: 43 additions & 0 deletions migration/versions/3be6a175f769_event_members.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""event_members
Revision ID: 3be6a175f769
Revises: 282efa88cdbc
Create Date: 2013-10-28 11:13:29.167744
"""
#
# # SAUCE - System for AUtomated Code Evaluation
# # Copyright (C) 2013 Moritz Schlarb
# #
# # This program is free software: you can redistribute it and/or modify
# # it under the terms of the GNU Affero General Public License as published by
# # the Free Software Foundation, either version 3 of the License, or
# # any later version.
# #
# # This program is distributed in the hope that it will be useful,
# # but WITHOUT ANY WARRANTY; without even the implied warranty of
# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# # GNU Affero General Public License for more details.
# #
# # You should have received a copy of the GNU Affero General Public License
# # along with this program. If not, see <http://www.gnu.org/licenses/>.
#

# revision identifiers, used by Alembic.
revision = '3be6a175f769'
down_revision = '282efa88cdbc'

from alembic import op
#from alembic.operations import Operations as op
import sqlalchemy as sa


def upgrade():
op.create_table('event_members',
sa.Column('user_id', sa.Integer, sa.ForeignKey('users.id'), primary_key=True),
sa.Column('event_id', sa.Integer, sa.ForeignKey('events.id'), primary_key=True),
)


def downgrade():
op.drop_table('event_members')
40 changes: 40 additions & 0 deletions migration/versions/425be68ff414_event_enroll.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""event_enroll
Revision ID: 425be68ff414
Revises: 3be6a175f769
Create Date: 2013-10-28 11:22:00.036581
"""
#
# # SAUCE - System for AUtomated Code Evaluation
# # Copyright (C) 2013 Moritz Schlarb
# #
# # This program is free software: you can redistribute it and/or modify
# # it under the terms of the GNU Affero General Public License as published by
# # the Free Software Foundation, either version 3 of the License, or
# # any later version.
# #
# # This program is distributed in the hope that it will be useful,
# # but WITHOUT ANY WARRANTY; without even the implied warranty of
# # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# # GNU Affero General Public License for more details.
# #
# # You should have received a copy of the GNU Affero General Public License
# # along with this program. If not, see <http://www.gnu.org/licenses/>.
#

# revision identifiers, used by Alembic.
revision = '425be68ff414'
down_revision = '3be6a175f769'

from alembic import op
#from alembic.operations import Operations as op
import sqlalchemy as sa


def upgrade():
op.add_column('events', sa.Column('enroll', sa.Enum('event', 'lesson', 'lesson_team', 'team', 'team_new', name='event_enroll'), nullable=True))


def downgrade():
op.drop_column('events', 'enroll')
23 changes: 13 additions & 10 deletions sauce/controllers/crc/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def __init__(self, query_modifier=None, query_modifiers=None,
if not hasattr(self, 'table_filler'):
class MyTableFiller(TableFiller):
__model__ = __entity__ = self.model
__actions__ = self.custom_actions
__actions__ = self.actions
__provider_type_selector_type__ = FilterSAORMSelector
query_modifier = self.query_modifier
query_modifiers = self.query_modifiers
Expand Down Expand Up @@ -276,31 +276,34 @@ class NewFiller(AddFormFiller):
# so we just use the imported DBSession here
super(FilterCrudRestController, self).__init__(DBSession, menu_items)

def custom_actions(self, obj):
''''Display bootstrap-styled action fields respecting the allow_* properties'''
result = []
count = 0
def _actions(self, obj):
''''Make list of action links respecting the allow_* properties'''
actions = []
try:
result.append(u'<a href="' + obj.url + '" class="btn btn-mini" title="Show">'
actions.append(u'<a href="' + obj.url + '" class="btn btn-mini" title="Show">'
u'<i class="icon-eye-open"></i></a>')
count += 1
except:
pass
if self.allow_edit:
try:
primary_fields = self.table_filler.__provider__.get_primary_fields(self.table_filler.__entity__)
pklist = u'/'.join(map(lambda x: unicode(getattr(obj, x)), primary_fields))
result.append(u'<a href="' + pklist + '/edit" class="btn btn-mini" title="Edit">'
actions.append(u'<a href="' + pklist + '/edit" class="btn btn-mini" title="Edit">'
u'<i class="icon-pencil"></i></a>')
except:
pass
if self.allow_delete:
result.append(
actions.append(
u'<a class="btn btn-mini btn-danger" href="./%d/delete" title="Delete">'
u' <i class="icon-remove icon-white"></i>'
u'</a>' % (obj.id))
return actions

def actions(self, obj):
''''Display bootstrap-styled action links respecting the allow_* properties'''
actions = self._actions(obj)
return literal('<div class="btn-group" style="width: %dpx;">'
% (len(result) * 30) + ''.join(result) + '</div>')
% (len(actions) * 30) + ''.join(actions) + '</div>')

def _before(self, *args, **kw):
super(FilterCrudRestController, self)._before(*args, **kw)
Expand Down
31 changes: 26 additions & 5 deletions sauce/controllers/crc/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@

from sauce.controllers.crc.base import FilterCrudRestController
from sauce.model import Event, Lesson
from sauce.widgets.widgets import MediumTextField
import sauce.lib.helpers as h

import tw2.core as twc
import tw2.bootstrap.forms as twb
import tw2.jqplugins.chosen.widgets as twjc
from formencode.validators import PlainText
from webhelpers.html.tags import link_to
Expand All @@ -39,14 +42,17 @@


class EventsCrudController(FilterCrudRestController):
'''CrudController for Events'''
'''CrudController for Events
TODO: Use tw2.dynforms to only display password field when enroll is not None
'''

model = Event

__table_options__ = {
'__omit_fields__': [
'id', 'description', 'password',
'_teacher', '_teacher_id',
'_teacher', '_teacher_id', '_members',
'_assignments', 'lessons', 'sheets', 'news',
],
'__field_order__': [
Expand All @@ -70,14 +76,17 @@ class EventsCrudController(FilterCrudRestController):
__form_options__ = {
'__omit_fields__': [
'id', 'type', '_assignments', 'sheets', 'news', 'lessons',
'password', 'teachers', '_teacher', '_teacher_id',
'teachers', '_teacher', '_teacher_id', '_members',
],
'__field_order__': [
'id', '_url', 'name', 'description',
'public', 'start_time', 'end_time',
'public', 'enroll', 'password',
'start_time', 'end_time',
],
'__field_widget_types__': {
'type': twjc.ChosenSingleSelectField,
'enroll': twb.RadioButtonTable,
'password': MediumTextField,
},
'__field_widget_args__': {
'_url': {
Expand All @@ -86,11 +95,23 @@ class EventsCrudController(FilterCrudRestController):
'public': {
'help_text': u'Make event visible for students',
},
'enroll': {
'name': 'enroll', 'id': 'enroll',
'cols': 3,
'options': [
('', 'None'), ('event', 'Event'), ('lesson', 'Lesson'),
#('lesson_team', 'Lesson/Team'),
('team', 'Team'), ('team_new', 'Team (Allow New Teams)'),
],
'value': 'None',
'help_text': u'Enrolling granularity.',
},
'password': {
'help_text': u'Password for student self-registration. Currently not implemented',
'help_text': u'Password for enrolling. If empty and enroll is not None, all students can enroll.',
},
},
'__field_validator_types__': {'_url': PlainText},
'__field_validators__': {'enroll': twc.validation.OneOfValidator(values=['event', 'lesson', 'lesson_team', 'team', 'team_new'])},
'__dropdown_field_names__': ['user_name', '_name', 'name', 'title'],
'__require_fields__': ['type', '_url'],
}
Expand Down
35 changes: 35 additions & 0 deletions sauce/controllers/crc/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class UsersCrudController(FilterCrudRestController):
'created',
'judgements',
'teached_events',
'_events',
] + (['new_password'] if _externalauth else []),
'__field_order__': [
'id',
Expand Down Expand Up @@ -181,6 +182,7 @@ class UsersCrudController(FilterCrudRestController):
'submissions', 'judgements',
'tutored_lessons', 'teached_events',
'teams', '_lessons',
'_events',
],
'__field_order__': [
'id',
Expand Down Expand Up @@ -213,6 +215,24 @@ def warn_externalauth_delete(*args, **kw):
flash('The profile will be created again when the users logs in the next time!', 'warn')


def unenroll(event, user):
try:
event._members.remove(user)
except ValueError:
pass
for l in event.lessons:
try:
l._members.remove(user)
except ValueError:
pass
for t in l.teams:
try:
t.members.remove(user)
except ValueError:
pass
return None


class StudentsCrudController(UsersCrudController):
'''CrudController for Students'''

Expand All @@ -231,6 +251,21 @@ class StudentsCrudController(UsersCrudController):
},
}

def _actions(self, obj):
actions = super(StudentsCrudController, self)._actions(obj)
if self.hints and 'event' in self.hints and self.hints['event']:
actions.insert(-1,
u'<a class="btn btn-mini btn-inverse" href="./%d/unenroll" title="Un-Enroll">'
u' <i class="icon-eject icon-white"></i>'
u'</a>' % (obj.id))
return actions

def __init__(self, *args, **kwargs):
event = kwargs.get('hints', {}).get('event', None)
if event:
self.__setters__['unenroll'] = ('null', lambda user: unenroll(event, user))
super(StudentsCrudController, self).__init__(*args, **kwargs)


class TutorsCrudController(UsersCrudController):
'''CrudController for Tutors'''
Expand Down
9 changes: 5 additions & 4 deletions sauce/controllers/event_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from sauce.model import Lesson, Team, User, Sheet, Assignment, Test, Event, NewsItem, DBSession
from sauce.controllers.crc.base import CrudIndexController
from sauce.controllers.crc import *
from sauce.model.user import lesson_members, team_members
from sauce.model.user import lesson_members, team_members, event_members
from sauce.model.event import lesson_tutors
import inspect
from sqlalchemy import or_
Expand Down Expand Up @@ -101,9 +101,10 @@ def __init__(self, event, **kwargs):
**kwargs)

self.students = StudentsCrudController(
query_modifier=lambda qry: (qry.join(lesson_members).join(Lesson)
#.filter(Lesson.id.in_(l.id for l in self.event.lessons))
.filter_by(event_id=self.event.id)
query_modifier=lambda qry: (qry.join(event_members).join(Event)
.filter_by(id=self.event.id)
.union(qry.join(lesson_members).join(Lesson)
.filter_by(event_id=self.event.id))
.union(qry.join(team_members).join(Team).join(Team.lesson)
.filter_by(event_id=self.event.id))
.distinct().order_by(User.id)),
Expand Down
Loading

0 comments on commit f00ce1a

Please sign in to comment.