Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit project #196

Merged
merged 86 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from 84 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
19d7502
paar fixes alvast
DRIESASTER Apr 27, 2024
e7244ce
almost
DRIESASTER Apr 27, 2024
21b95d3
files uploaden werkt
DRIESASTER Apr 29, 2024
385c070
formatting fixes
DRIESASTER Apr 29, 2024
8347bc2
Merge branch 'dev' into create_project_fixes
DRIESASTER Apr 29, 2024
8bc6d02
bugfixes + added date fields in db
DRIESASTER May 2, 2024
c464723
formatting
DRIESASTER May 2, 2024
12debbc
Merge branch 'dev' into create_project_fixes
DRIESASTER May 2, 2024
c5161cb
background container gone + no instructors found fix
DRIESASTER May 5, 2024
6d0be0e
format
DRIESASTER May 5, 2024
146a15c
basic
DRIESASTER May 6, 2024
3d8a0f4
temp
DRIESASTER May 6, 2024
be39aa8
begint ergens op te trekken
DRIESASTER May 7, 2024
7eb8355
update werkt!
DRIESASTER May 9, 2024
d4aca38
small change
DRIESASTER May 9, 2024
4118629
Merge branch 'dev' into create_project_fixes
DRIESASTER May 9, 2024
c21f95b
Merge branch 'create_project_fixes' into edit_project
DRIESASTER May 9, 2024
210519e
looking good
DRIESASTER May 9, 2024
0d0e761
edit fixes (almost there)
DRIESASTER May 10, 2024
e32f0ee
files werken
DRIESASTER May 10, 2024
9d0a622
edit (improvements)
DRIESASTER May 11, 2024
20fba3f
updates
DRIESASTER May 12, 2024
a92f4aa
Merge remote-tracking branch 'origin/edit_project' into edit_project
DRIESASTER May 12, 2024
ecc38de
filestructure visible
DRIESASTER May 12, 2024
994b6f7
werkt
DRIESASTER May 12, 2024
652f1b2
af?
DRIESASTER May 12, 2024
d0704ce
Merge branch 'dev' into edit_project
DRIESASTER May 12, 2024
5c9d742
remove empty alembic revisions
reyniersbram May 12, 2024
b47a2db
docker terug ok
DRIESASTER May 13, 2024
607a322
alert
DRIESASTER May 13, 2024
a875d86
route fix
DRIESASTER May 13, 2024
3e1fce1
Merge remote-tracking branch 'origin/edit_project' into edit_project
DRIESASTER May 13, 2024
e01ac3e
Merge branch 'dev' into edit_project
DRIESASTER May 13, 2024
4d588e6
linter
DRIESASTER May 13, 2024
5096f0d
add publish_date to mock projects
reyniersbram May 16, 2024
21bf7b9
reroute
DRIESASTER May 16, 2024
5b808dc
Merge remote-tracking branch 'origin/edit_project' into edit_project
DRIESASTER May 16, 2024
1438842
bijna
DRIESASTER May 16, 2024
453304a
requirements
DRIESASTER May 16, 2024
3b904e7
editfiles werkt
DRIESASTER May 16, 2024
dc32d29
Merge branch 'dev' into edit_project
DRIESASTER May 17, 2024
9abfe8a
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…
DRIESASTER May 17, 2024
65cce88
few fixes
DRIESASTER May 18, 2024
0fbeff0
small time fix
DRIESASTER May 18, 2024
8611181
i18n
DRIESASTER May 18, 2024
af1b36e
Merge branch 'dev' into edit_project
DRIESASTER May 18, 2024
a4c5d23
format
DRIESASTER May 18, 2024
cac4038
formatting
DRIESASTER May 18, 2024
5b03ad9
final
DRIESASTER May 18, 2024
939d5a6
final
DRIESASTER May 18, 2024
3e7b315
Merge branch 'dev' into edit_project
reyniersbram May 19, 2024
532482b
Merge branch 'dev' into edit_project
DRIESASTER May 19, 2024
8445c42
vage fixes
DRIESASTER May 19, 2024
f5e6ff1
Merge remote-tracking branch 'origin/edit_project' into edit_project
DRIESASTER May 19, 2024
c282d60
vage fixes 2
DRIESASTER May 19, 2024
12c2998
tijd update nu ook
DRIESASTER May 19, 2024
fa39167
test fix
DRIESASTER May 19, 2024
426f6f8
format
DRIESASTER May 19, 2024
2c87b56
schema optional added
DRIESASTER May 19, 2024
6e86cd4
Merge branch 'dev' into edit_project
DRIESASTER May 20, 2024
dc66f0d
fix voor tests not found
DRIESASTER May 20, 2024
815aa3d
bram requests xoxo
DRIESASTER May 20, 2024
6643475
whoops
DRIESASTER May 20, 2024
18caa76
Update frontend/src/components/RequirementsInput.vue
DRIESASTER May 20, 2024
979cc72
Update frontend/src/components/RequirementsInput.vue
DRIESASTER May 20, 2024
0c7c095
Update frontend/src/queries/Project.ts
DRIESASTER May 20, 2024
fe138a0
Update frontend/src/queries/Project.ts
DRIESASTER May 20, 2024
d800d90
Update frontend/src/components/RequirementsInput.vue
DRIESASTER May 20, 2024
1def3c0
Update frontend/src/services/project.ts
DRIESASTER May 20, 2024
6e54506
Update frontend/src/components/project/DatePicker.vue
DRIESASTER May 20, 2024
cda3ea9
fixes
DRIESASTER May 20, 2024
330bf99
Merge remote-tracking branch 'origin/edit_project' into edit_project
DRIESASTER May 20, 2024
a630739
loading fix
DRIESASTER May 20, 2024
607153b
Update frontend/src/components/project/DatePicker.vue
DRIESASTER May 20, 2024
93964d1
fixes vooral cleanup
DRIESASTER May 20, 2024
6219dba
Merge remote-tracking branch 'origin/edit_project' into edit_project
DRIESASTER May 20, 2024
fd1ecea
fixes vooral cleanup
DRIESASTER May 20, 2024
a833f1c
fixes vooral cleanup
DRIESASTER May 20, 2024
ada4bae
Revert "upgrade @vue/test-utils (#181)"
DRIESASTER May 20, 2024
d9a0a64
luxon weg?
DRIESASTER May 20, 2024
e49a433
luxon echt weg?
DRIESASTER May 20, 2024
3e4d692
luxon echt echt weg?
DRIESASTER May 20, 2024
7a48a68
renaming + unused gone
DRIESASTER May 20, 2024
fda6d1d
Merge branch 'dev' into edit_project
DRIESASTER May 20, 2024
2ea6979
some small fixes
reyniersbram May 20, 2024
4dd6ff5
bramfixes
DRIESASTER May 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

```sh
# Create a python virtual environment
python -m venv venv
python3.12 -m venv venv
# Activate the environment
source venv/bin/activate
# Install dependencies
Expand Down
2 changes: 0 additions & 2 deletions backend/src/project/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ async def retrieve_project(project_id: int,


async def retrieve_test_files_uuid(project: Project = Depends(retrieve_project)):
if project.test_files_uuid is None:
raise TestsNotFound
return project.test_files_uuid


Expand Down
9 changes: 6 additions & 3 deletions backend/src/project/router.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Sequence, List
from typing import Sequence, List, Optional

from docker import DockerClient
from fastapi import APIRouter, Depends, UploadFile, BackgroundTasks
Expand Down Expand Up @@ -96,7 +96,9 @@ async def get_submissions_dump(project_id: int, db: AsyncSession = Depends(get_a


@router.get("/{project_id}/test_files")
async def get_test_files(test_files_uuid: str = Depends(retrieve_test_files_uuid)):
async def get_test_files(test_files_uuid: Optional[str] = Depends(retrieve_test_files_uuid)):
if not test_files_uuid:
return []
return get_files_from_dir(tests_path(test_files_uuid))


Expand All @@ -116,7 +118,8 @@ async def put_test_files(

if not using_default_docker_image(uuid):
# build custom docker image if dockerfile is present
background_tasks.add_task(build_docker_image, tests_path(uuid), uuid, client)
background_tasks.add_task(
build_docker_image, tests_path(uuid), uuid, client)

return await update_test_files(db, project.id, uuid)

Expand Down
5 changes: 4 additions & 1 deletion backend/src/project/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class ProjectBase(BaseModel):
is_visible: bool = Field(default=True)
DRIESASTER marked this conversation as resolved.
Show resolved Hide resolved
capacity: int = Field(gt=0)
requirements: List[Requirement] = []
enroll_deadline: Optional[datetime]
publish_date: datetime
DRIESASTER marked this conversation as resolved.
Show resolved Hide resolved


class ProjectCreate(ProjectBase):
Expand Down Expand Up @@ -55,7 +57,8 @@ class ProjectUpdate(BaseModel):
deadline: Optional[datetime] = None
description: Optional[str] = None
requirements: Optional[List[Requirement]] = None
is_visible: Optional[bool] = None
enroll_deadline: Optional[datetime] = None
publish_date: Optional[datetime] = None

@field_validator("deadline")
def validate_deadline(cls, value: datetime) -> datetime:
Expand Down
9 changes: 6 additions & 3 deletions backend/src/project/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ async def create_project(db: AsyncSession, project_in: ProjectCreate) -> Project
description=project_in.description,
is_visible=project_in.is_visible,
capacity=project_in.capacity,
requirements=[Requirement(**r.model_dump()) for r in project_in.requirements],
requirements=[Requirement(**r.model_dump())
for r in project_in.requirements],
)
db.add(new_project)
await db.commit()
Expand Down Expand Up @@ -77,10 +78,12 @@ async def update_project(
project.name = project_update.name
if project_update.deadline is not None:
project.deadline = project_update.deadline
if project_update.publish_date is not None:
project.publish_date = project_update.publish_date
if project_update.enroll_deadline is not None:
project.enroll_deadline = project.enroll_deadline
if project_update.description is not None:
project.description = project_update.description
if project_update.is_visible is not None:
project.is_visible = project_update.is_visible
if project_update.requirements is not None:
await delete_requirements_for_project(db, project_id)
project.requirements = [Requirement(**r.model_dump())
Expand Down
3 changes: 2 additions & 1 deletion backend/src/subject/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ async def is_instructor(db: AsyncSession, subject_id: int, uid: str) -> bool:


async def create_subject(db: AsyncSession, subject: SubjectCreate) -> Subject:
db_subject = Subject(name=subject.name, academic_year=subject.academic_year)
db_subject = Subject(
name=subject.name, academic_year=subject.academic_year)
db.add(db_subject)
await db.commit()
await db.refresh(db_subject)
Expand Down
1 change: 1 addition & 0 deletions backend/tests/test_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"capacity": 1,
"requirements": [{"mandatory": "true", "value": "*.py"}],
"test_files": [],
"publish_date": future_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
}

group_data = {"team_name": "test group", "project_id": 0}
Expand Down
27 changes: 1 addition & 26 deletions backend/tests/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
"deadline": future_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"description": "test",
"enroll_deadline": future_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"is_visible": True,
"capacity": 1,
"requirements": [{"mandatory": "false", "value": "*.pdf"}],
"publish_date": future_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
}


Expand Down Expand Up @@ -111,28 +111,3 @@ async def test_patch_project(client: AsyncClient, db: AsyncSession, project_id:
assert response.status_code == 200
response = await client.get(f"/api/projects/{project_id}")
assert response.json()["description"] == "new description"


@pytest.mark.asyncio
async def test_is_visible_project(client: AsyncClient, db: AsyncSession, project_id: int):

response = await client.get(f"/api/projects/{project_id}")
subject_id = response.json()["subject_id"]

await set_admin(db, "test", True)
await client.patch(f"/api/projects/{project_id}", json={"is_visible": False})
await set_admin(db, "test", False)

response = await client.get(f"/api/projects/{project_id}")
assert response.status_code == 404 # Not found as project is not visible

response = await client.get(f"/api/subjects/{subject_id}/projects")
assert len(response.json()["projects"]) == 0

# Now privileged get request
await make_instructor(subject_id, "test", db, client)
response = await client.get(f"/api/projects/{project_id}")
assert response.status_code == 200

response = await client.get(f"/api/subjects/{subject_id}/projects")
assert len(response.json()["projects"]) == 1
1 change: 1 addition & 0 deletions backend/tests/test_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"is_visible": True,
"capacity": 1,
"requirements": [{"mandatory": "true", "value": "*.py"}, {"mandatory": "false", "value": "*.pdf"}],
"publish_date": future_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
}

group_data = {"team_name": "test group", "project_id": 0}
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/components/form_elements/FilesInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,18 @@ function onDeleteClick(index: number) {
.files {
margin-top: 15px;
}
.custom-alert .alert-text {
white-space: nowrap; /* Prevents the text from wrapping */
overflow: hidden; /* Prevents overflow of text outside the alert box */
text-overflow: ellipsis; /* Adds an ellipsis if the text overflows */
}

.custom-alert a {
display: inline; /* Ensures the link is in line with other text */
white-space: normal; /* Allows normal wrapping inside the link if needed */
}

.custom-alert {
margin-bottom: 15px; /* Added spacing between the alert and the button */
}
</style>
56 changes: 46 additions & 10 deletions frontend/src/components/project/DatePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,66 @@
></v-text-field>
</template>
<v-date-picker v-model="date" no-title></v-date-picker>
<v-time-picker v-model="time" format="24hr"></v-time-picker>
DRIESASTER marked this conversation as resolved.
Show resolved Hide resolved
</v-menu>
</template>

<script setup lang="ts">
import { ref, computed } from "vue";
import { ref, watch, computed, defineEmits } from "vue";
import { VTimePicker } from "vuetify/labs/VTimePicker";

const date = defineModel<Date>({});

defineProps<{
// Define props and emits
const props = defineProps<{
modelValue: Date; // This expects a JavaScript Date object
label: string;
}>();
const emit = defineEmits(["update:modelValue"]);

const menuVisible = ref<boolean>(false);
const date = ref<Date>(new Date(props.modelValue || Date.now())); // Initialize with current date or modelValue
const time = ref<string>(formatTime(props.modelValue || new Date())); // Initialize with current time or modelValue

// Watcher to sync changes in modelValue to date and time pickers
watch(
() => props.modelValue,
(newValue, oldValue) => {
if (newValue && newValue !== oldValue) {
date.value = new Date(newValue); // Update the date picker
time.value = formatTime(new Date(newValue)); // Update the time picker
}
},
{ immediate: true, deep: true }
);

// Format time into a string
function formatTime(date: Date): string {
return `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
}

const menuVisible = ref(false);
// Watcher to emit updates when date or time changes
watch(
[date, time],
() => {
const [hours, minutes] = time.value.split(":").map(Number);
const updatedDate = new Date(date.value);
updatedDate.setHours(hours, minutes, 0, 0); // Apply time changes to the date
emit("update:modelValue", new Date(updatedDate)); // Emit the updated Date object
},
{ deep: true }
);

// Computed to format the date as ISO string (just the date part)
// Computed property to display date and time in text field
const displayDate = computed(() => {
if (date.value) {
const selectedDate = new Date(date.value.getTime());
selectedDate.setMinutes(selectedDate.getMinutes() - selectedDate.getTimezoneOffset());
return selectedDate.toISOString().substring(0, 10);
if (date.value && time.value) {
const selectedDate = new Date(date.value);
const [hours, minutes] = time.value.split(":").map(Number);
selectedDate.setHours(hours, minutes);
return `${selectedDate.getFullYear()}-${(selectedDate.getMonth() + 1).toString().padStart(2, "0")}-${selectedDate.getDate().toString().padStart(2, "0")} ${selectedDate.getHours().toString().padStart(2, "0")}:${selectedDate.getMinutes().toString().padStart(2, "0")}`;
}
return "";
});

// Toggle visibility of date/time picker
function toggleDatePicker() {
menuVisible.value = !menuVisible.value;
}
Expand Down
91 changes: 91 additions & 0 deletions frontend/src/components/project/DisplayTestFiles.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<template>
<v-card class="file-display-container" outlined>
<v-card-title>{{ $t("project.testfiles") }}</v-card-title>
<v-card-text>
<v-treeview v-model="tree" :items="treeItems" activatable hoverable open-on-click>
<template v-slot:prepend="{ item }">
<v-icon>{{ getFileIcon(item.title) }}</v-icon>
</template>
</v-treeview>
</v-card-text>
</v-card>
</template>

<script setup>
import { computed, ref } from "vue";
import { VTreeview } from "vuetify/labs/VTreeview";

const props = defineProps({
files: Array,
});

const tree = ref([]);
const treeItems = computed(() => {
const rootNode = {};
props.files.forEach((file) => {
const parts = file.filename.split("/").filter(Boolean);
let current = rootNode;

parts.forEach((part, index) => {
if (!current[part]) {
current[part] = {
_isFile: index === parts.length - 1,
children: {},
title: part,
};
}
if (index === parts.length - 1) {
current[part]._isFile = true;
current[part].title = parts.slice(-1)[0];
current[part].id = file.path;
current[part].children = undefined;
} else {
current = current[part].children;
}
});
});
return buildTree(rootNode);
});

function buildTree(node, path = "") {
const result = [];
Object.keys(node).forEach((key) => {
if (!node[key]._isFile && node[key].children) {
const fullPath = path ? `${path}/${key}` : key;
result.push({
title: node[key].title,
id: fullPath,
children: buildTree(node[key].children, fullPath),
});
} else if (node[key]._isFile) {
result.push({
title: node[key].title,
id: node[key].id,
});
}
});
return result;
}

const icons = ref({
html: "mdi-language-html5",
js: "mdi-nodejs",
json: "mdi-code-json",
md: "mdi-language-markdown",
pdf: "mdi-file-pdf-box",
png: "mdi-file-image",
txt: "mdi-file-document-outline",
xls: "mdi-file-excel",
folder: "mdi-folder",
folderOpen: "mdi-folder-open",
});

function getFileIcon(filename) {
const extension = filename.split(".").pop();
if (extension === filename) {
return "mdi-folder";
} else {
return icons.value[extension] || "mdi-file";
}
}
</script>
2 changes: 1 addition & 1 deletion frontend/src/components/project/ProjectSideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
class="group-button"
:email="subject!.email"
></NeedHelpButton>
<router-link v-if="isTeacher" :to="`/projects/${project!.id}/edit`">
<router-link v-if="isTeacher" :to="`/project/${project!.id}/edit`">
<v-btn class="group-button" prepend-icon="mdi-pencil">
{{ $t("project.edit") }}
</v-btn>
Expand Down
Loading
Loading