diff --git a/server/controllers/admin.py b/server/controllers/admin.py index 037116383..6dda9ebed 100644 --- a/server/controllers/admin.py +++ b/server/controllers/admin.py @@ -421,6 +421,7 @@ def edit_category(cid, category_id): if request.method == 'POST' and form.validate(editing=True): if form.delete.data == True: category.archive() + db.session.delete(category) db.session.commit() msg = "Category \"{}\" was deleted.".format(category.name) flash(msg, "error") @@ -438,15 +439,10 @@ def edit_category(cid, category_id): @is_staff(course_arg='cid') def course_assignments(cid): courses, current_course = get_courses(cid) - categories = current_course.categories - uncategorized = Assignment.query.filter_by( - course=current_course, - category=None - ).all() return render_template('staff/course/assignment/assignments.html', current_course=current_course, - categories=categories, - uncategorized=uncategorized) + categories=current_course.categories, + uncategorized=current_course.uncategorized) @admin.route("/course//assignments/new", methods=["GET", "POST"]) @is_staff(course_arg='cid') @@ -490,6 +486,8 @@ def assignment(cid, aid): cache.delete_memoized(Assignment.name_to_assign_info) db.session.commit() flash("Assignment edited successfully.", "success") + else: + form.set_defaults() return render_template('staff/course/assignment/assignment.html', assignment=assign, form=form, courses=courses, diff --git a/server/controllers/student.py b/server/controllers/student.py index 5c34aab38..8ed45546a 100644 --- a/server/controllers/student.py +++ b/server/controllers/student.py @@ -88,8 +88,14 @@ def course(offering): 'inactive': [a.user_status(current_user) for a in course.assignments if not a.active and a.visible] } + + user_assigns = lambda assigns: [a.user_status(current_user) for a in assigns if a.visible] + categories = [(c, user_assigns(c.assignments)) for c in course.categories] + uncategorized = user_assigns(course.uncategorized) + print(uncategorized) return render_template('student/course/index.html', course=course, - **assignments) + categories=categories, + uncategorized=uncategorized) @student.route('//') diff --git a/server/forms.py b/server/forms.py index c1d6f3dd2..4eb267847 100644 --- a/server/forms.py +++ b/server/forms.py @@ -158,28 +158,10 @@ def validate(self, editing=False): class AssignmentForm(BaseForm): - def __init__(self, course, obj=None, **kwargs): - self.course = course - self.obj = obj - super(AssignmentForm, self).__init__(obj=obj, **kwargs) - if obj: - if obj.due_date == self.due_date.data: - self.due_date.data = utils.local_time_obj(obj.due_date, course) - if obj.lock_date == self.lock_date.data: - self.lock_date.data = utils.local_time_obj( - obj.lock_date, course) - - # dynamically set values - categories = Category.query.filter_by( - course=course - ).order_by(Category.name).all() - self.category.choices = [(str(c.id), c.name) for c in categories] - self.category.choices.insert(0, ('0', 'Uncategorized')) - self.category.default = 'Uncategorized' - display_name = StringField('Name', validators=[validators.required()]) category = SelectField('Grading Category') + points = IntegerField('Points') name = StringField(description='Endpoint', validators=[validators.required()]) due_date = DateTimeField('Due Date (Course Time)', @@ -204,6 +186,26 @@ def __init__(self, course, obj=None, **kwargs): visible = BooleanField('Visible On Student Dashboard', default=True) autograding_key = StringField('Autograder Key', validators=[validators.optional()]) + + def __init__(self, course, obj=None, **kwargs): + self.course = course + super(AssignmentForm, self).__init__(obj=obj, **kwargs) + + # dynamically set values + self.category.choices = [(str(c.id), c.name) for c in course.categories] + self.category.choices.insert(0, ('0', 'Uncategorized')) + + self.obj = obj + if obj: + self.due_date.data = utils.local_time_obj(obj.due_date, self.course) + self.lock_date.data = utils.local_time_obj(obj.lock_date, self.course) + + def set_defaults(self): + if self.obj: + category = self.obj.category + self.category.data = str(category and category.id or 0) + + @property def endpoint(self): return '{}/{}'.format(self.course.offering, self.display_name.data) @@ -211,12 +213,13 @@ def endpoint(self): def populate_obj(self, obj): """ Updates obj attributes based on form contents. """ category_id = int(self.category.data) - if self.category.data: - self.category.data = Category.query.get(category_id) - super(AssignmentForm, self).populate_obj(obj) + self.category.data = Category.query.get(category_id) + super().populate_obj(obj) obj.due_date = utils.server_time_obj(self.due_date.data, self.course) obj.lock_date = utils.server_time_obj(self.lock_date.data, self.course) obj.name = self.endpoint + self.obj = obj + self.category.data = str(category_id) def validate(self): if not super(AssignmentForm, self).validate(): diff --git a/server/models.py b/server/models.py index bd68caa0e..8a0ef12d0 100644 --- a/server/models.py +++ b/server/models.py @@ -275,6 +275,20 @@ def __repr__(self): def by_name(name): return Course.query.filter_by(offering=name).one_or_none() + @property + def categories(self): + return Category.query.filter_by( + course=self + ).order_by(Category.name).all() + + @property + def uncategorized(self): + return Assignment.query.filter_by( + course=self, + category=None # uncategorized + ).all() + + @property def display_name_with_semester(self): year = self.offering[-2:] @@ -358,9 +372,9 @@ class Category(Model): e.g. "Drop lowest X", "Cap at X points", "Never drop X" """ id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(255), index=True, default="Uncategorized") + name = db.Column(db.String(255), index=True) course_id = db.Column(db.ForeignKey("course.id"), index=True, nullable=False) - points = db.Column(db.Integer, default=0.0) + points = db.Column(db.Integer, default=0) visible = db.Column(db.Boolean, default=True) ceil = db.Column(db.Boolean, default=True) # assignments (from Assignment backref) @@ -382,12 +396,8 @@ def can(cls, obj, user, action): return is_staff def archive(self): - uncategorized = Category.query.filter_by( - course=self.course, - name='Uncategorized' - ).first() - for assignment in self.assignments: - assignment.category = uncategorized + for assignment in self.assignments[:]: + assignment.category = None class Assignment(Model): @@ -492,19 +502,19 @@ def assignment_stats(assign_id, detailed=True): }) return stats - @staticmethod + @classmethod @cache.memoize(1000) - def name_to_assign_info(name): - assign = Assignment.query.filter_by(name=name).one_or_none() + def name_to_assign_info(cls, name): + assign = cls.query.filter_by(name=name).one_or_none() if assign: info = assign.as_dict() info['active'] = assign.active return info - @staticmethod - def by_name(name): + @classmethod + def by_name(cls, name): """ Return assignment object when given a name.""" - return Assignment.query.filter_by(name=name).one_or_none() + return cls.query.filter_by(name=name).one_or_none() def user_timeline(self, user_id, current_backup_id=None): """ Timeline of user submissions. Returns a dictionary diff --git a/server/static/css/student.css b/server/static/css/student.css index 54d154d0f..1fe172506 100644 --- a/server/static/css/student.css +++ b/server/static/css/student.css @@ -20,6 +20,9 @@ i, br { font-weight:300; letter-spacing:normal; } +.subcontent { + margin-bottom: 30px; +} .content h2 { font-size:2em; font-weight:300; @@ -201,6 +204,9 @@ section { table { width:100%; } + .subcontent .wrap table { + background-color: #f6f6f6; + } tr.header { color:#767676; } @@ -433,40 +439,45 @@ header .logo { } .invite-input { - width: 95%; - margin:1.5em 0 0 1em; + + width: 300px; + margin:1em 0 1.5em 14px; color:#666; outline: none; border-top: none; border-left: none; border-right: none; - border-bottom-width: 1px; - border-bottom-style: dashed; - border-bottom-color: rgb(153, 153, 153); + padding: 14px 0; + border-bottom: 1px solid #ccc; + } + .invite-input:focus { + border-bottom: 1px solid black; + color: black; } .btn-invite { - margin:1em 0 0 0; + margin-top: 2em; } .btn-accept { margin-bottom: 1em; outline: none; } +.subcontent.list .wrap { + margin-bottom: 2em; +} .content h1 { padding:0 14px; } .content h2 { - padding:0 14px 10px 14px; + padding:0 14px; } .cell { - padding:2em; + padding: 1em 2em; + background-color:#f6f6f6; + font-size: 0.9em; } .upload-cell { background-color: #f6f6f6; } - -.cell:nth-child(even) { - background-color:#f6f6f6; -} .cell-title { font-size:1.5em; } diff --git a/server/templates/staff/course/assignment/assignment.html b/server/templates/staff/course/assignment/assignment.html index 0f75c0397..dcc3cda4d 100644 --- a/server/templates/staff/course/assignment/assignment.html +++ b/server/templates/staff/course/assignment/assignment.html @@ -150,8 +150,8 @@

Edit {{ assignment.display_name }} Assignment

{% call forms.render_form(form, action_url="", action_text='Update Assignment', class_='form') %} {{ forms.render_field(form.display_name, label_visible=true, placeholder='Hog', type='text') }} {{ forms.render_field(form.name, label_visible=false, value=current_course.offering + '/hog', type='text', id="assignment-name") }} - {{ forms.render_field(form.category, label_visible=true, - required="required", type='text', id="category") }} + {{ forms.render_field(form.category, label_visible=true, required="required", type='text', id="category") }} + {{ forms.render_field(form.points, label_visible=true, required="required", type='text', id="points") }} {{ forms.render_field(form.max_group_size, label_visible=true, type='number', min='1') }} {{ forms.render_field(form.due_date, label_visible=true, placeholder=utils.new_due_date(current_course), type='text', class='form-control datepicker') }} {{ forms.render_field(form.lock_date, label_visible=true, placeholder=utils.new_lock_date(current_course), type='text', class='form-control datepicker') }} diff --git a/server/templates/student/course/_assigntable.html b/server/templates/student/course/_assigntable.html index d7467e754..06f096a56 100644 --- a/server/templates/student/course/_assigntable.html +++ b/server/templates/student/course/_assigntable.html @@ -1,209 +1,214 @@ -{% macro render_assign(assignments, course, tname="Assignments", empty=False, show_scores=False) %} - {% if assignments %} - - {% elif empty %} +{% macro render_categories(course, categories, uncategorized, tname="Assignments", empty=False, show_scores=False) %} - {% endif %} {% endmacro %} -{% macro render_assign_mobile(assignments, course, tname="Assignments", admin=False, empty=False, show_scores=False) %} - {% if assignments %} - + {% endfor %} +{% endmacro %} - {% elif empty %} +{% macro render_categories_mobile(course, categories, uncategorized, tname="Assignments", admin=False, empty=False, show_scores=False) %} - {% endif %} {% endmacro %} diff --git a/server/templates/student/course/index.html b/server/templates/student/course/index.html index a562886b5..a269df2b2 100644 --- a/server/templates/student/course/index.html +++ b/server/templates/student/course/index.html @@ -24,13 +24,11 @@

{{ course.display_name_with_semester}}

- {{ table.render_assign(active, course=course, tname="Current Assignments", empty=True) }} - {{ table.render_assign(inactive, course=course, tname="Past Assignments", show_scores=True) }} - + {{ table.render_categories(course, categories, uncategorized, empty=False) }} + - {{ table.render_assign_mobile(active, course=course, tname="Current Assignments", empty=True) }} - {{ table.render_assign_mobile(inactive,course=course, tname="Past Assignments", show_scores=True) }} + {{ table.render_categories_mobile(course, categories, uncategorized, empty=False) }}