Skip to content

Commit 5b873be

Browse files
committed
added bacon integation inchallenges
1 parent 37f4c66 commit 5b873be

9 files changed

+263
-53
lines changed

website/challenge_signals.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ def update_challenge_progress(user, challenge_title, model_class, reason, thresh
3838

3939
team.team_points += challenge.points
4040
team.save()
41+
42+
# Award BACON tokens to all team members
43+
from website.feed_signals import giveBacon
44+
45+
for team_member in team.user_profiles.all():
46+
if team_member.user:
47+
giveBacon(team_member.user, amt=challenge.bacon_reward)
4148
else:
4249
if user not in challenge.participants.all():
4350
challenge.participants.add(user)
@@ -56,6 +63,11 @@ def update_challenge_progress(user, challenge_title, model_class, reason, thresh
5663
# Award points to the user
5764
Points.objects.create(user=user, score=challenge.points, reason=reason)
5865

66+
# Award BACON tokens for completing the challenge
67+
from website.feed_signals import giveBacon
68+
69+
giveBacon(user, amt=challenge.bacon_reward)
70+
5971
except Challenge.DoesNotExist:
6072
pass
6173

@@ -155,6 +167,11 @@ def handle_sign_in_challenges(user, user_profile):
155167
reason=f"Completed '{challenge_title}' challenge",
156168
)
157169

170+
# Award BACON tokens for completing the challenge
171+
from website.feed_signals import giveBacon
172+
173+
giveBacon(user, amt=challenge.bacon_reward)
174+
158175
except Challenge.DoesNotExist:
159176
# Handle case when the challenge does not exist
160177
pass
@@ -194,6 +211,13 @@ def handle_team_sign_in_challenges(team):
194211
# Add points to the team
195212
team.team_points += challenge.points
196213
team.save()
214+
215+
# Award BACON tokens to all team members
216+
from website.feed_signals import giveBacon
217+
218+
for team_member in team.user_profiles.all():
219+
if team_member.user:
220+
giveBacon(team_member.user, amt=challenge.bacon_reward)
197221
except Challenge.DoesNotExist:
198222
print(f"Challenge '{challenge_title}' does not exist.")
199223
pass
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated manually for adding bacon_reward field to Challenge model
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("website", "0177_alter_challenge_team_participants"),
9+
]
10+
11+
operations = [
12+
migrations.AddField(
13+
model_name="challenge",
14+
name="bacon_reward",
15+
field=models.IntegerField(default=5),
16+
),
17+
]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Generated manually to update bacon rewards for existing challenges
2+
3+
from django.db import migrations
4+
5+
6+
def update_challenge_bacon_rewards(apps, schema_editor):
7+
Challenge = apps.get_model("website", "Challenge")
8+
9+
# Update team challenges with higher bacon rewards
10+
team_challenge_rewards = {
11+
"Report 10 IPs": 10,
12+
"Report 10 Issues": 10,
13+
"All Members Sign in for 5 Days": 15,
14+
}
15+
16+
# Update single user challenges with appropriate bacon rewards
17+
single_challenge_rewards = {
18+
"Report 5 IPs": 5,
19+
"Report 5 Issues": 5,
20+
"Sign in for 5 Days": 8,
21+
}
22+
23+
# Update team challenges
24+
for title, bacon_amount in team_challenge_rewards.items():
25+
Challenge.objects.filter(title=title, challenge_type="team").update(bacon_reward=bacon_amount)
26+
27+
# Update single challenges
28+
for title, bacon_amount in single_challenge_rewards.items():
29+
Challenge.objects.filter(title=title, challenge_type="single").update(bacon_reward=bacon_amount)
30+
31+
32+
def reverse_update_challenge_bacon_rewards(apps, schema_editor):
33+
# Reset all bacon rewards to default value of 5
34+
Challenge = apps.get_model("website", "Challenge")
35+
Challenge.objects.all().update(bacon_reward=5)
36+
37+
38+
class Migration(migrations.Migration):
39+
dependencies = [
40+
("website", "0178_add_bacon_reward_to_challenge"),
41+
]
42+
43+
operations = [
44+
migrations.RunPython(update_challenge_bacon_rewards, reverse_update_challenge_bacon_rewards),
45+
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated by Django 5.1.8 on 2025-07-21 21:01
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("website", "0242_labs"),
9+
]
10+
11+
operations = [
12+
migrations.AddField(
13+
model_name="challenge",
14+
name="bacon_reward",
15+
field=models.IntegerField(default=5),
16+
),
17+
]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Generated by Django 5.1.8 on 2025-07-21 21:01
2+
3+
from django.db import migrations
4+
5+
6+
def update_challenge_bacon_rewards(apps, schema_editor):
7+
"""Update existing challenges with appropriate bacon rewards"""
8+
Challenge = apps.get_model("website", "Challenge")
9+
10+
# Define bacon rewards for different challenges
11+
challenge_rewards = {
12+
"Report 5 IPs": 10,
13+
"Report 5 Issues": 15,
14+
"Sign in for 5 Days": 5,
15+
"Report 10 IPs": 20,
16+
"Report 10 Issues": 25,
17+
"All Members Sign in for 5 Days": 10,
18+
}
19+
20+
# Update existing challenges
21+
for challenge_title, bacon_reward in challenge_rewards.items():
22+
try:
23+
challenge = Challenge.objects.get(title=challenge_title)
24+
challenge.bacon_reward = bacon_reward
25+
challenge.save()
26+
print(f"Updated challenge '{challenge_title}' with {bacon_reward} bacon reward")
27+
except Challenge.DoesNotExist:
28+
print(f"Challenge '{challenge_title}' not found, skipping...")
29+
30+
31+
def reverse_update_challenge_bacon_rewards(apps, schema_editor):
32+
"""Reverse operation - reset bacon_reward to default value"""
33+
Challenge = apps.get_model("website", "Challenge")
34+
Challenge.objects.all().update(bacon_reward=5)
35+
36+
37+
class Migration(migrations.Migration):
38+
dependencies = [
39+
("website", "0243_add_bacon_reward_to_challenge"),
40+
]
41+
42+
operations = [
43+
migrations.RunPython(
44+
update_challenge_bacon_rewards,
45+
reverse_update_challenge_bacon_rewards,
46+
),
47+
]

website/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1602,6 +1602,7 @@ class Challenge(models.Model):
16021602
Organization, related_name="team_challenges", blank=True
16031603
) # For team challenges
16041604
points = models.IntegerField(default=0) # Points for completing the challenge
1605+
bacon_reward = models.IntegerField(default=5) # BACON tokens earned for completing the challenge
16051606
progress = models.IntegerField(default=0) # Progress in percentage
16061607
completed = models.BooleanField(default=False)
16071608
completed_at = models.DateTimeField(null=True, blank=True)

website/templates/team_challenges.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
</div>
1212
<div>
1313
<h1 class="text-3xl font-bold text-gray-800">Team Challenges</h1>
14-
<p class="text-gray-600 mt-2">Complete challenges with your team to earn points and climb the leaderboard</p>
14+
<p class="text-gray-600 mt-2">
15+
Complete challenges with your team to earn points, BACON tokens, and climb the leaderboard
16+
</p>
1517
</div>
1618
</div>
1719
</div>
@@ -29,8 +31,12 @@ <h1 class="text-3xl font-bold text-gray-800">Team Challenges</h1>
2931
</div>
3032
<div>
3133
<h3 class="text-lg font-bold text-gray-800">{{ challenge.title }}</h3>
32-
<div class="flex items-center gap-2 mt-1">
34+
<div class="flex items-center gap-4 mt-1">
3335
<span class="text-sm font-medium text-[#e74c3c]">{{ challenge.points }} points</span>
36+
<div class="flex items-center gap-1">
37+
<i class="fas fa-bacon text-[#e74c3c] text-sm"></i>
38+
<span class="text-sm font-medium text-[#e74c3c]">{{ challenge.bacon_reward }} BACON</span>
39+
</div>
3440
{% if challenge.progress >= 100 %}
3541
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
3642
<i class="fas fa-check-circle mr-1"></i>
Lines changed: 99 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,106 @@
11
{% extends "base.html" %}
22
{% block content %}
33
{% include "includes/sidenav.html" %}
4-
<div class="text-center mx-auto my-5 p-5 bg-gray-100 rounded-lg max-w-4xl shadow-md">
5-
<h2 class="text-xl font-bold">Single User Challenges</h2>
6-
{% if challenges %}
7-
<div>
8-
{% for challenge in challenges %}
9-
<div class="my-4 p-4 bg-white rounded-lg border border-gray-300 text-left cursor-pointer shadow-sm hover:shadow-md transition-shadow duration-300"
10-
onclick="toggleDetails({{ challenge.id }})">
11-
<div class="flex justify-between items-center">
12-
<span class="text-lg font-bold flex-grow">{{ challenge.title }}</span>
13-
<span class="text-sm text-gray-500 font-bold mr-1">{{ challenge.points }} pts</span>
14-
<span id="dropdown-icon-{{ challenge.id }}"
15-
class="fas fa-chevron-down text-base transition-transform duration-300"></span>
16-
</div>
17-
<div id="challenge-details-{{ challenge.id }}"
18-
class="hidden mt-2 text-sm text-gray-600">
19-
<p>{{ challenge.description }}</p>
20-
</div>
21-
<!-- Progress Bar always visible -->
22-
<div class="relative w-full bg-gray-300 rounded mt-2 h-5">
23-
<div class="h-full bg-red-600 rounded transition-all duration-500"
24-
id="progress-bar-{{ challenge.id }}"
25-
data-progress="{{ challenge.progress }}"></div>
26-
<span class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 font-bold text-gray-800">{{ challenge.progress|floatformat:0 }}%</span>
27-
</div>
4+
<div class="container mx-auto px-4 py-8">
5+
<div class="max-w-4xl mx-auto">
6+
<!-- Header Section -->
7+
<div class="bg-white rounded-2xl shadow-lg p-8 mb-8">
8+
<div class="flex items-center gap-6">
9+
<div class="w-16 h-16 bg-[#e74c3c] rounded-full flex items-center justify-center">
10+
<i class="fas fa-user-check text-2xl text-white"></i>
2811
</div>
29-
{% endfor %}
12+
<div>
13+
<h1 class="text-3xl font-bold text-gray-800">Individual Challenges</h1>
14+
<p class="text-gray-600 mt-2">Complete personal challenges to earn points, BACON tokens, and showcase your skills</p>
15+
</div>
16+
</div>
3017
</div>
31-
{% else %}
32-
<p class="text-gray-700">No challenges available.</p>
33-
{% endif %}
18+
{% if challenges %}
19+
<div class="space-y-4">
20+
{% for challenge in challenges %}
21+
<div class="bg-white rounded-xl shadow-md hover:shadow-lg transition-all duration-300 overflow-hidden"
22+
x-data="{ open: false }">
23+
<!-- Challenge Header -->
24+
<div class="p-6 cursor-pointer" @click="open = !open">
25+
<div class="flex items-center justify-between">
26+
<div class="flex items-center gap-4">
27+
<div class="w-12 h-12 bg-[#e74c3c] bg-opacity-10 rounded-lg flex items-center justify-center">
28+
<i class="fas fa-medal text-[#e74c3c] text-xl"></i>
29+
</div>
30+
<div>
31+
<h3 class="text-lg font-bold text-gray-800">{{ challenge.title }}</h3>
32+
<div class="flex items-center gap-4 mt-1">
33+
<span class="text-sm font-medium text-[#e74c3c]">{{ challenge.points }} points</span>
34+
<div class="flex items-center gap-1">
35+
<i class="fas fa-bacon text-[#e74c3c] text-sm"></i>
36+
<span class="text-sm font-medium text-[#e74c3c]">{{ challenge.bacon_reward }} BACON</span>
37+
</div>
38+
{% if challenge.progress >= 100 %}
39+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
40+
<i class="fas fa-check-circle mr-1"></i>
41+
Completed
42+
</span>
43+
{% endif %}
44+
</div>
45+
</div>
46+
</div>
47+
<div class="flex items-center gap-4">
48+
<!-- Progress Circle -->
49+
<div class="relative w-12 h-12">
50+
<svg class="w-12 h-12 transform -rotate-90">
51+
<circle class="text-gray-200" stroke-width="3" stroke="currentColor" fill="transparent" r="20" cx="24" cy="24" />
52+
<circle class="text-[#e74c3c]" stroke-width="3" stroke-linecap="round" stroke="currentColor" fill="transparent" r="20" cx="24" cy="24" style="stroke-dasharray: {{ challenge.stroke_dasharray }}; stroke-dashoffset: {{ challenge.stroke_dashoffset }}" />
53+
</svg>
54+
<span class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-sm font-medium">
55+
{{ challenge.progress|floatformat:0 }}%
56+
</span>
57+
</div>
58+
<i class="fas fa-chevron-down text-gray-400 transform transition-transform duration-300"
59+
:class="{ 'rotate-180': open }"></i>
60+
</div>
61+
</div>
62+
</div>
63+
<!-- Challenge Details -->
64+
<div class="overflow-hidden transition-all duration-300 max-h-0"
65+
x-ref="content"
66+
x-bind:style="open ? 'max-height: ' + $refs.content.scrollHeight + 'px' : ''">
67+
<div class="p-6 pt-0 border-t border-gray-100">
68+
<p class="text-gray-600">{{ challenge.description }}</p>
69+
<div class="mt-4 flex items-center justify-between">
70+
<div class="flex items-center gap-2">
71+
<div class="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center">
72+
<i class="fas fa-user text-gray-400"></i>
73+
</div>
74+
<span class="text-sm font-medium text-gray-700">Personal Challenge</span>
75+
</div>
76+
{% if challenge.progress < 100 %}
77+
<button class="px-4 py-2 bg-[#e74c3c] text-white rounded-lg hover:bg-[#c0392b] transition-colors duration-300">
78+
Start Challenge
79+
</button>
80+
{% else %}
81+
<div class="px-4 py-2 bg-green-100 text-green-800 rounded-lg">
82+
<i class="fas fa-trophy mr-2"></i>
83+
Challenge Completed!
84+
</div>
85+
{% endif %}
86+
</div>
87+
</div>
88+
</div>
89+
</div>
90+
{% endfor %}
91+
</div>
92+
{% else %}
93+
<!-- No Challenges State -->
94+
<div class="bg-white rounded-xl shadow-md p-8 text-center">
95+
<div class="w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-full flex items-center justify-center">
96+
<i class="fas fa-clipboard-list text-2xl text-gray-400"></i>
97+
</div>
98+
<h3 class="text-xl font-semibold text-gray-800 mb-2">No Challenges Available</h3>
99+
<p class="text-gray-600">There are no individual challenges available at the moment. Check back later!</p>
100+
</div>
101+
{% endif %}
102+
</div>
34103
</div>
35-
<script>
36-
document.addEventListener('DOMContentLoaded', function() {
37-
// Set progress bar widths based on data-progress attribute
38-
const progressBars = document.querySelectorAll('[data-progress]');
39-
progressBars.forEach(bar => {
40-
const progress = bar.getAttribute('data-progress');
41-
bar.style.width = progress + '%';
42-
});
43-
});
44-
45-
function toggleDetails(challengeId) {
46-
const detailsElement = document.getElementById(`challenge-details-${challengeId}`);
47-
const iconElement = document.getElementById(`dropdown-icon-${challengeId}`);
48-
49-
if (detailsElement.classList.contains("hidden")) {
50-
detailsElement.classList.remove("hidden");
51-
iconElement.classList.add("transform", "rotate-180");
52-
} else {
53-
detailsElement.classList.add("hidden");
54-
iconElement.classList.remove("transform", "rotate-180");
55-
}
56-
}
57-
</script>
104+
<!-- Alpine.js for animations -->
105+
<script src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
58106
{% endblock content %}

website/views/user.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,11 @@ def get(self, request):
974974
# If the user is not participating, set progress to 0
975975
challenge.progress = 0
976976

977+
# Calculate the progress circle offset (same as team challenges)
978+
circumference = 125.6
979+
challenge.stroke_dasharray = circumference
980+
challenge.stroke_dashoffset = circumference - (circumference * challenge.progress / 100)
981+
977982
return render(
978983
request,
979984
"user_challenges.html",

0 commit comments

Comments
 (0)