-
-
-
- {items.length == 0 && name ?
-
- : null}
-
-
dispatch(setDesc(e.target.value))}
- />
-
-
-
-
-
-
-
-
-
dispatch(setPoints(e.value))}
- />
-
-
- dispatch(setVisible(e.value))} />
-
-
-
-
+ const handleVisibleChange = (e) => {
+ setVisibleDate(e.value);
+ console.log(`visible change ${visibleDate} ${e.value}`)
+ let d = e.value.toISOString().replace("Z", "");
+ dispatch(setVisible(d));
+ };
+
+ const handleHiddenChange = (e) => {
+ console.log(`hidden change ${hiddenDate} ${e.value}`)
+ let d = e.value.toISOString().replace("Z", "");
+ dispatch(setHidden(d));
+ };
+
+ return (
+
+
+
+
+
+ {items.length === 0 && name ? (
+
+ ) : null}
+
+
dispatch(setDesc(e.target.value))}
+ />
+
+
+
+
+
+
+
+ dispatch(setPoints(e.value))}
+ />
+
+
+
+
+
+
+
+
+
+
+
- );
+
+
+ );
}
diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/state/assignment/assignSlice.js b/bases/rsptx/assignment_server_api/assignment_builder/src/state/assignment/assignSlice.js
index 4c45e594..43484769 100644
--- a/bases/rsptx/assignment_server_api/assignment_builder/src/state/assignment/assignSlice.js
+++ b/bases/rsptx/assignment_server_api/assignment_builder/src/state/assignment/assignSlice.js
@@ -82,10 +82,14 @@ export const createAssignment = createAsyncThunk(
// make a date that is acceptable on the server
let duedate = new Date(assignData.duedate);
duedate = duedate.toISOString().replace('Z', '');
+ let visibledate = new Date(assignData.visibledate).toISOString().replace('Z', '');
+ let hiddingdate = new Date(assignData.hiddingdate).toISOString().replace('Z', '');
let body = {
name: assignData.name,
description: assignData.description,
duedate: duedate,
+ visibledate: visibledate,
+ hiddingdate: hiddingdate,
points: assignData.points,
kind: "quickcode",
};
@@ -303,7 +307,10 @@ let epoch = cDate.getTime();
epoch = epoch + 60 * 60 * 24 * 7 * 1000;
cDate = new Date(epoch);
let defaultDeadline = cDate.toLocaleString();
-// old
+let visibleEpoch = epoch - 60 * 60 * 24 * 7 * 1000;
+let hiddenEpoch = epoch + 60 * 60 * 24 * 7 * 1000;
+let visibleDateDefault = new Date(visibleEpoch).toLocaleString();
+let hiddingDateDefault = new Date(hiddenEpoch).toLocaleString();
// create a slice for Assignments
// This slice must be registered with the store in store.js
@@ -319,6 +326,7 @@ let defaultDeadline = cDate.toLocaleString();
* - setId
* - setPoints
* - setVisible
+ * - setHidden
* - setIsPeer
* - setIsTimed
* - setNoFeedback
@@ -371,6 +379,7 @@ let defaultDeadline = cDate.toLocaleString();
* @property {Function} reducers.setDesc - Sets the description of the assignment.
* @property {Function} reducers.setDue - Sets the due date of the assignment.
* @property {Function} reducers.setVisible - Sets the visibility of the assignment.
+ * @property {Function} reducers.setHidden - Sets the visibility of the assignment.
* @property {Function} reducers.setIsPeer - Sets if the assignment is a peer assignment.
* @property {Function} reducers.setIsTimed - Sets if the assignment is timed.
* @property {Function} reducers.setNoFeedback - Sets if the assignment has no feedback.
@@ -398,7 +407,8 @@ export const assignSlice = createSlice({
desc: "",
duedate: defaultDeadline,
points: 1,
- visible: true,
+ visibledate: visibleDateDefault,
+ hiddingdate: hiddingDateDefault,
is_peer: false,
is_timed: false,
nofeedback: true,
@@ -437,8 +447,25 @@ export const assignSlice = createSlice({
state.duedate = action.payload.toISOString().replace('Z', '')
},
setVisible: (state, action) => {
- state.visible = action.payload;
+ // action.payload is a Date object coming from the date picker or a string from the server
+ // convert it to a string and remove the Z because we don't expect timezone information
+ if (typeof action.payload === "string") {
+ state.visibledate = action.payload;
+ return;
+ }
+ state.visibledate = action.payload.toISOString().replace('Z', '')
+ },
+
+ setHidden: (state, action) => {
+ // action.payload is a Date object coming from the date picker or a string from the server
+ // convert it to a string and remove the Z because we don't expect timezone information
+ if (typeof action.payload === "string") {
+ state.hiddingdate = action.payload;
+ return;
+ }
+ state.hiddingdate = action.payload.toISOString().replace('Z', '')
},
+
setIsPeer: (state, action) => {
state.is_peer = action.payload;
},
@@ -596,6 +623,7 @@ export const {
setReleased,
setTimeLimit,
setVisible,
+ setHidden,
sumPoints,
updateExercise,
updateField,
@@ -627,5 +655,6 @@ export const selectPoints = (state) => state.assignment.points;
export const selectQuestionCount = (state) => state.assignment.question_count;
export const selectSearchResults = (state) => state.assignment.search_results;
export const selectTimeLimit = (state) => state.assignment.time_limit;
-export const selectVisible = (state) => state.assignment.visible;
+export const selectVisible = (state) => state.assignment.visibledate;
+export const selectHidden = (state) => state.assignment.hiddingdate;
export default assignSlice.reducer;
diff --git a/bases/rsptx/assignment_server_api/routers/instructor.py b/bases/rsptx/assignment_server_api/routers/instructor.py
index fbfaef04..f1418319 100644
--- a/bases/rsptx/assignment_server_api/routers/instructor.py
+++ b/bases/rsptx/assignment_server_api/routers/instructor.py
@@ -169,6 +169,7 @@ async def new_assignment(
**request_data.model_dump(),
course=course.id,
visible=True,
+ hidden=False,
released=False,
from_source=False,
is_peer=False,
diff --git a/bases/rsptx/assignment_server_api/routers/student.py b/bases/rsptx/assignment_server_api/routers/student.py
index 6e3d33a7..5a07aa84 100644
--- a/bases/rsptx/assignment_server_api/routers/student.py
+++ b/bases/rsptx/assignment_server_api/routers/student.py
@@ -84,7 +84,7 @@ async def get_assignments(
assignments = await fetch_assignments(course.course_name, is_visible=True)
assignments.sort(key=lambda x: x.duedate, reverse=True)
stats_list = await fetch_all_assignment_stats(course.course_name, user.id)
- stats = {}
+ stats = {s.assignment: s for s in stats_list}
for s in stats_list:
stats[s.assignment] = s
rslogger.debug(f"stats: {stats}")
@@ -206,11 +206,12 @@ async def doAssignment(
return RedirectResponse("/assignment/student/chooseAssignment")
- if (
- assignment.visible == "F"
- or assignment.visible is None
- or assignment.visible == False
- ):
+ current_time = datetime.datetime.utcnow()
+
+ if not (assignment.visibledate <= current_time and
+
+ (assignment.hideDate is None or assignment.hideDate >= current_time)):
+
if await is_instructor(request) is False:
rslogger.error(
f"Attempt to access invisible assignment {assignment_id} by {user.username}"
@@ -219,6 +220,7 @@ async def doAssignment(
if assignment.points is None:
assignment.points = 0
+
# This query assumes that questions are on a page and in a subchapter that is
# present in the book. For many questions that is of course a given. But for
diff --git a/components/rsptx/db/crud.py b/components/rsptx/db/crud.py
index 3daa90d5..fa57628a 100644
--- a/components/rsptx/db/crud.py
+++ b/components/rsptx/db/crud.py
@@ -1260,6 +1260,7 @@ async def fetch_assignments(
course_name: str,
is_peer: Optional[bool] = False,
is_visible: Optional[bool] = False,
+ is_hidden: Optional[bool] = True,
fetch_all: Optional[bool] = False,
) -> List[AssignmentValidator]:
"""
@@ -1271,10 +1272,13 @@ async def fetch_assignments(
:param is_peer: bool, whether or not the assignment is a peer assignment
:return: List[AssignmentValidator], a list of AssignmentValidator objects
"""
+ now = datetime.utcnow()
+ # Visibility clause
if is_visible:
- vclause = Assignment.visible == is_visible
- else:
+ vclause = or_(Assignment.visible == is_visible, and_(Assignment.visibledate >= now, Assignment.hiddingdate <= now)
+ )
+ else:
vclause = True
if is_peer:
@@ -1301,13 +1305,12 @@ async def fetch_assignments(
.order_by(Assignment.duedate.desc())
)
+
async with async_session() as session:
res = await session.execute(query)
- rslogger.debug(f"{res=}")
return [AssignmentValidator.from_orm(a) for a in res.scalars()]
-
-# write a function that fetches all Assignment objects given a course name
+# write a function that fetches one Assignment objects given a course name
async def fetch_one_assignment(assignment_id: int) -> AssignmentValidator:
"""
Fetch one Assignment object
@@ -1316,8 +1319,14 @@ async def fetch_one_assignment(assignment_id: int) -> AssignmentValidator:
:return: AssignmentValidator
"""
-
- query = select(Assignment).where(Assignment.id == assignment_id)
+ now = datetime.utcnow()
+ query = select(Assignment).where(
+ and_(
+ Assignment.id == assignment_id,
+ Assignment.visibledate <= now,
+ Assignment.hiddingdate >= now
+ )
+ )
async with async_session() as session:
res = await session.execute(query)
@@ -1601,9 +1610,9 @@ async def fetch_questions_by_search_criteria(
if criteria.question_type:
where_criteria.append(Question.question_type == criteria.question_type)
if criteria.author:
- where_criteria.append(Question.author.regexp_match(criteria.author, flags="i"))
+ where_criteria.append(Question.author.regexp_match(criteria.author, flags="i"))
if criteria.base_course:
- where_criteria.append(Question.base_course == criteria.base_course)
+ where_criteria.append(Question.base_course == criteria.base_course)
if len(where_criteria) == 0:
raise ValueError("No search criteria provided")
diff --git a/components/rsptx/db/models.py b/components/rsptx/db/models.py
index 03b5de79..3c13f25b 100644
--- a/components/rsptx/db/models.py
+++ b/components/rsptx/db/models.py
@@ -554,8 +554,11 @@ class Assignment(Base, IdMixin):
released = Column(Web2PyBoolean, nullable=False)
description = Column(Text)
duedate = Column(DateTime, nullable=False)
+ # visible = Column(Web2PyBoolean, nullable=False)
+ threshold_pct = Column(Float(53))
visible = Column(Web2PyBoolean, nullable=False)
- threshold_pct = Column(Float(53))
+ visibledate = Column(DateTime, nullable=False)
+ hiddingdate = Column(DateTime, nullable=False)
allow_self_autograde = Column(Web2PyBoolean)
is_timed = Column(Web2PyBoolean)
time_limit = Column(Integer)
diff --git a/components/rsptx/validation/schemas.py b/components/rsptx/validation/schemas.py
index 5abe238c..c52fd7f5 100644
--- a/components/rsptx/validation/schemas.py
+++ b/components/rsptx/validation/schemas.py
@@ -228,6 +228,8 @@ class AssignmentIncoming(BaseModel):
description: str
points: int
duedate: datetime
+ visibledate: datetime
+ hiddingdate: datetime
class QuestionIncoming(BaseModel):