Skip to content

initial changes for optional challenges #743

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

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions dojo_plugin/api/v1/scoreboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def get_scoreboard_for(model, duration):
Solves.date >= datetime.datetime.utcnow() - datetime.timedelta(days=duration)
if duration else True
)
required_filter = DojoChallenges.required == True
solves = db.func.count().label("solves")
rank = (
db.func.row_number()
Expand All @@ -49,6 +50,7 @@ def get_scoreboard_for(model, duration):
query = (
model.solves()
.filter(duration_filter)
.filter(required_filter)
.group_by(*user_entities)
.order_by(rank)
.with_entities(rank, solves, *user_entities)
Expand Down
16 changes: 10 additions & 6 deletions dojo_plugin/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,12 @@ def solves(self, **kwargs):

def completions(self):
solves_subquery = (
self.solves(ignore_visibility=True, ignore_admins=False)
self.solves(ignore_visibility=True, ignore_admins=False, required_only=True)
.with_entities(Solves.user_id,
db.func.count().label("solve_count"),
db.func.max(Solves.date).label("last_solve"))
.group_by(Solves.user_id)
.having(db.func.count() == len(self.challenges))
.having(db.func.count() == len([challenge for challenge in self.challenges if challenge.required]))
.subquery()
)
return (
Expand All @@ -271,7 +271,7 @@ def awards(self):
return awards

def completed(self, user):
return self.solves(user=user, ignore_visibility=True, ignore_admins=False).count() == len(self.challenges)
return self.solves(user=user, ignore_visibility=True, ignore_admins=False, required_only=True).count() == len([challenge for challenge in self.challenges if challenge.required])

def is_admin(self, user=None):
if user is None:
Expand Down Expand Up @@ -430,8 +430,8 @@ def path(self):
def assessments(self):
return [assessment for assessment in (self.dojo.course or {}).get("assessments", []) if assessment.get("id") == self.id]

def visible_challenges(self, user=None):
return [challenge for challenge in self.challenges if challenge.visible() or self.dojo.is_admin(user=user)]
def visible_challenges(self, user=None, required_only=False):
return [challenge for challenge in self.challenges if (not required_only or challenge.required) and (challenge.visible() or self.dojo.is_admin(user=user))]

def solves(self, **kwargs):
return DojoChallenges.solves(module=self, **kwargs)
Expand Down Expand Up @@ -473,6 +473,7 @@ class DojoChallenges(db.Model):
id = db.Column(db.String(32), index=True, nullable=False)
name = db.Column(db.String(128))
description = db.Column(db.Text)
required = db.Column(db.Boolean, default=True)

data = db.Column(JSONB)
data_fields = ["image", "path_override", "importable", "allow_privileged", "progression_locked", "survey"]
Expand Down Expand Up @@ -543,7 +544,7 @@ def visible(cls, when=None):
))

@hybrid_method
def solves(self, *, user=None, dojo=None, module=None, ignore_visibility=False, ignore_admins=True):
def solves(self, *, user=None, dojo=None, module=None, ignore_visibility=False, ignore_admins=True, required_only=False):
result = (
Solves.query
.join(DojoChallenges, and_(
Expand Down Expand Up @@ -587,6 +588,9 @@ def solves(self, *, user=None, dojo=None, module=None, ignore_visibility=False,
if module:
result = result.filter(DojoChallenges.module == module)

if required_only:
result = result.filter(DojoChallenges.required)

return result

@property
Expand Down
2 changes: 2 additions & 0 deletions dojo_plugin/utils/dojo.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
Optional("allow_privileged"): bool,
Optional("importable"): bool,
Optional("progression_locked"): bool,
Optional("required", default=True): bool,
Optional("auxiliary", default={}, ignore_extra_keys=True): dict,
# Optional("path"): Regex(r"^[^\s\.\/][^\s\.]{,255}$"),

Expand Down Expand Up @@ -395,6 +396,7 @@ def import_ids(attrs, *datas):
module_data.get("id"), challenge_data.get("id"), transfer=challenge_data.get("transfer", None)
) if "import" not in challenge_data else None,
progression_locked=challenge_data.get("progression_locked"),
required=challenge_data.get("required"),
visibility=visibility(DojoChallengeVisibilities, dojo_data, module_data, challenge_data),
survey=shadow("survey", dojo_data, module_data, challenge_data, default=None),
default=(assert_import_one(DojoChallenges.from_id(*import_ids(["dojo", "module", "challenge"], dojo_data, module_data, challenge_data)),
Expand Down
4 changes: 2 additions & 2 deletions dojo_theme/templates/dojo.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ <h2 class="row">Modules</h2>
<ul class="card-list">
{% for module in dojo.modules %}
{% set container_count = module_container_counts.get(module.id, 0) %}
{% set num_challs = module.visible_challenges() | length %}
{% set num_challs = module.visible_challenges(required_only=True) | length %}
{% set num_solved = module.solves(user=user, ignore_visibility=True, ignore_admins=False).count() if user else 0 %}
{% if module.visible() or dojo.is_admin(user) %}
{{ card(
Expand All @@ -71,7 +71,7 @@ <h2 class="row">Modules</h2>
),
"hidden" if not module.visible()
],
solve_percent=(num_solved / num_challs) * 100 if num_challs else 0,
solve_percent=(1 if num_solved > num_challs else (num_solved / num_challs)) * 100 if num_challs else 0,
) }}
{% endif %}
{% endfor %}
Expand Down
9 changes: 8 additions & 1 deletion dojo_theme/templates/module.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,13 @@ <h4 class="accordion-item-name challenge-name {{ active }}">
<span class="invisible-placeholder pr-2">{{ challenge.name or challenge.id }}</span>
</div>
{% else %}
<span class="pr-2">{{ challenge.name or challenge.id }}</span>
<span class="pr-2">
{% if not challenge.required %}
<i>{{ challenge.name or challenge.id }}</i>
{% else %}
{{ challenge.name or challenge.id }}
{% endif %}
</span>
{% endif %}
{% if dojo.is_admin() and (progression_locked or hidden) %}
<small><small><small>
Expand Down Expand Up @@ -117,6 +123,7 @@ <h4 class="accordion-item-name challenge-name {{ active }}">
{% if lock_challenge %}
<p><em>This challenge is locked</em></p>
{% else %}
{% if not challenge.required %}<small>This challenge is optional, it will not count towards dojo completion.</small><br><br>{% endif %}
{{ challenge.description | markdown }}
{% endif %}
</div>
Expand Down