问题
I have the following problem: A user is taking a course on my website. A course consists of several chapters and each chapter consists of several steps. If the user is logged in and completes a step, the backend saves the progress. When the user comes back to the site, I want him to continue where he left off. To do this I have to add the css class "active" to the corresponding navigation step and the content tab of each chapter. My starting point was to test with an if statement if the user had already finished the step. That worked great, but adds .active to all the steps that have not been finished yet. I only want to add it to the first step that hasn't been finished yet. I have actually solved this problem but it is a rather brute hack and I had to include a custom template tag that allows me to set a variable in the template:
{% load custom_tags %}
{% for chapter in chapters %}
<div>
<ul class="unstyled wizard clearfix">
{% set active_wizard_steps = 0 %}
{% for step in chapter.steps.all %}
<li class="{% if not step.read and active_wizard_steps == 0 %}active{% set active_wizard_steps = 1 %}{% endif %}{% if step.read %} finished{% endif %}"></li>
{% endfor %}
</ul>
</div>
{% endfor %}
I know that this can also be done by passing a list or dict to the template with all the active steps and then testing in the template if a step is in that list. But that would have meant rewriting a lot of view code so I decided to do it completely in the template.
So my question is: what would be the best practice way to solve this problem? I'm asking just out of curiousity and because the problem was actually really fun to figure out. Thanks a lot!
回答1:
Add a method to Step:
class Step(models.Model):
# ...
def is_active(self):
# do your sauce
And in the template:
class="{% if step.is_active %}active{% endif %}"
We already have convenient helpers like get_absolute_url in models, why not add this one ?
If you don't like it, you could do a template filter and use it as such:
class="{% if step|is_active %}active{% endif %}"
Update
Here's another method, make a template filter as such:
@register.filter
def set_active_steps(steps):
active_wizard_steps = 0
for step in steps:
if not step.read and active_wizard_steps == 0:
step.is_active = True
return steps
Then, in your template:
{% for step in chapter.steps.all|set_active_steps %}
... {% if step.is_active %}...{% endif %}
{% endfor %}
回答2:
I know you already solved your issue, I'm posting another answer for the sake of completion.
If you need inside a {% for %}{% endfor %}
to set a class by matching the current wizard step against the current item in the loop, you can use the following code:
{% for step in wizard.steps.all %}
<li class="{% if wizard.steps.step1 == forloop.counter %}active{% elif wizard.steps.step1 > forloop.counter %} complete{% endif %}">
<span class="step">{{ forloop.counter }}</span>
</li>
{% endfor %}
来源:https://stackoverflow.com/questions/12458239/django-template-tags-set-active-on-the-first-element-of-a-forloop-that-satisfi