Skip to content

Commit 403342b

Browse files
authored
Merge branch 'develop' into new-dubious
2 parents e99e4ac + a72368f commit 403342b

File tree

10 files changed

+282
-90
lines changed

10 files changed

+282
-90
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Generated by Django 3.2.23 on 2025-10-12 17:33
2+
3+
import django.core.validators
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('applications', '0057_auto_20250203_2145'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='hackerapplication',
16+
name='cv_flagged',
17+
field=models.BooleanField(default=False),
18+
),
19+
migrations.AlterField(
20+
model_name='hackerapplication',
21+
name='diet',
22+
field=models.CharField(choices=[('', '- Select a diet -'), ('None', 'No requirements'), ('Vegetarian', 'Vegetarian'), ('Vegan', 'Vegan'), ('Gluten-free', 'Gluten-free'), ('Others', 'Others')], default='None', max_length=300),
23+
),
24+
migrations.AlterField(
25+
model_name='hackerapplication',
26+
name='lennyface',
27+
field=models.CharField(default='-.-', max_length=20),
28+
),
29+
migrations.AlterField(
30+
model_name='hackerapplication',
31+
name='phone_number',
32+
field=models.CharField(blank=True, max_length=16, null=True, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '+#########'. Up to 16 digits allowed.", regex='^\\+?1?\\d{9,15}$')]),
33+
),
34+
migrations.AlterField(
35+
model_name='hackerapplication',
36+
name='tshirt_size',
37+
field=models.CharField(choices=[('', '- Select a t-shirt size -'), ('XS', 'Unisex - XS'), ('S', 'Unisex - S'), ('M', 'Unisex - M'), ('L', 'Unisex - L'), ('XL', 'Unisex - XL'), ('XXL', 'Unisex - XXL'), ('XXXL', 'Unisex - XXXL')], default='', max_length=300),
38+
),
39+
migrations.AlterField(
40+
model_name='mentorapplication',
41+
name='diet',
42+
field=models.CharField(choices=[('', '- Select a diet -'), ('None', 'No requirements'), ('Vegetarian', 'Vegetarian'), ('Vegan', 'Vegan'), ('Gluten-free', 'Gluten-free'), ('Others', 'Others')], default='None', max_length=300),
43+
),
44+
migrations.AlterField(
45+
model_name='mentorapplication',
46+
name='lennyface',
47+
field=models.CharField(default='-.-', max_length=20),
48+
),
49+
migrations.AlterField(
50+
model_name='mentorapplication',
51+
name='phone_number',
52+
field=models.CharField(blank=True, max_length=16, null=True, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '+#########'. Up to 16 digits allowed.", regex='^\\+?1?\\d{9,15}$')]),
53+
),
54+
migrations.AlterField(
55+
model_name='mentorapplication',
56+
name='tshirt_size',
57+
field=models.CharField(choices=[('', '- Select a t-shirt size -'), ('XS', 'Unisex - XS'), ('S', 'Unisex - S'), ('M', 'Unisex - M'), ('L', 'Unisex - L'), ('XL', 'Unisex - XL'), ('XXL', 'Unisex - XXL'), ('XXXL', 'Unisex - XXXL')], default='', max_length=300),
58+
),
59+
migrations.AlterField(
60+
model_name='sponsorapplication',
61+
name='diet',
62+
field=models.CharField(choices=[('', '- Select a diet -'), ('None', 'No requirements'), ('Vegetarian', 'Vegetarian'), ('Vegan', 'Vegan'), ('Gluten-free', 'Gluten-free'), ('Others', 'Others')], default='None', max_length=300),
63+
),
64+
migrations.AlterField(
65+
model_name='sponsorapplication',
66+
name='tshirt_size',
67+
field=models.CharField(choices=[('', '- Select a t-shirt size -'), ('XS', 'Unisex - XS'), ('S', 'Unisex - S'), ('M', 'Unisex - M'), ('L', 'Unisex - L'), ('XL', 'Unisex - XL'), ('XXL', 'Unisex - XXL'), ('XXXL', 'Unisex - XXXL')], default='', max_length=300),
68+
),
69+
migrations.AlterField(
70+
model_name='volunteerapplication',
71+
name='lennyface',
72+
field=models.CharField(default='-.-', max_length=20),
73+
),
74+
migrations.AlterField(
75+
model_name='volunteerapplication',
76+
name='phone_number',
77+
field=models.CharField(blank=True, max_length=16, null=True, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '+#########'. Up to 16 digits allowed.", regex='^\\+?1?\\d{9,15}$')]),
78+
),
79+
migrations.AlterField(
80+
model_name='volunteerapplication',
81+
name='tshirt_size',
82+
field=models.CharField(choices=[('', '- Select a t-shirt size -'), ('XS', 'Unisex - XS'), ('S', 'Unisex - S'), ('M', 'Unisex - M'), ('L', 'Unisex - L'), ('XL', 'Unisex - XL'), ('XXL', 'Unisex - XXL'), ('XXXL', 'Unisex - XXXL')], default='', max_length=300),
83+
),
84+
]

applications/models/hacker.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class HackerApplication(BaseApplication):
7171
hardware = models.CharField(max_length=300, null=True, blank=True)
7272

7373
cvs_edition = models.BooleanField(default=False)
74+
cv_flagged = models.BooleanField(default=False)
7475

7576
resume = models.FileField(
7677
upload_to=resume_path_hackers,
@@ -84,11 +85,16 @@ def annotate_vote(cls, qs):
8485
return qs.annotate(vote_avg=Avg("vote__calculated_vote"))
8586

8687
def invalidate(self):
88+
"""
89+
Marks the application as invalid, but only if its current status is "dubious".
90+
Also, if the user has a team, it deletes it.
91+
"""
8792
if self.status != APP_DUBIOUS:
88-
raise ValidationError(
89-
"Applications can only be marked as invalid if they are dubious first"
90-
)
91-
self.status = APP_INVALID
93+
raise ValidationError('Applications can only be marked as invalid if they are dubious first')
94+
self.status = APP_INVALID
95+
team = getattr(self.user, 'team', None)
96+
if team:
97+
team.delete()
9298
self.save()
9399

94100
def set_dubious(self, user, dubious_type, dubious_comment_text):
@@ -110,14 +116,26 @@ def unset_dubious(self):
110116
self.dubious_type = DUBIOUS_NONE
111117
self.dubious_comment = None
112118
self.save()
113-
119+
120+
def set_flagged_cv(self):
121+
"""Sets the CV as flagged for review. If there was an accepted
122+
resume, deletes it so it can be reviewed."""
123+
self.cv_flagged = True
124+
if hasattr(self, 'acceptedresume'):
125+
self.acceptedresume.delete()
126+
self.save()
127+
114128
def set_contacted(self, user):
115129
if not self.contacted:
116130
self.contacted = True
117131
self.contacted_by = user
118132
self.save()
119133

120134
def confirm_blacklist(self, user, motive_of_ban):
135+
"""
136+
Confirms the application as blacklisted, but only if its current status is "APP_BLACKLISTED".
137+
Also, if the user has a team, it deletes it.
138+
"""
121139
if self.status != APP_BLACKLISTED:
122140
raise ValidationError(
123141
"Applications can only be confirmed as blacklisted if they are blacklisted first"
@@ -130,6 +148,9 @@ def confirm_blacklist(self, user, motive_of_ban):
130148
self.user, motive_of_ban
131149
)
132150
blacklist_user.save()
151+
team = getattr(self.user, 'team', None)
152+
if team:
153+
team.delete()
133154
self.save()
134155

135156
def set_blacklist(self):

meals/templates/meal_checkin.html

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -45,33 +45,36 @@ <h1 style="font-size: larger">{{ meal_name }}</h1>
4545

4646
<video id="qr-video" style="width: 100%; height: 100%; "></video>
4747
<script nonce="{{ request.csp_nonce }}">
48-
// set timer bool
49-
isReady = true;
48+
let isReady = true;
49+
let lastScannedQR = null;
50+
let lastScanTime = 0;
5051

51-
// Load qr video and add function to execute when scans a qr
5252
document.addEventListener('DOMContentLoaded', () => {
5353
let scanner = new Scanner('qr-video', (content) => {
54-
if (content.data && isReady) {
55-
SuperScan('{% url 'cool_meals_api' %}', content.data, scanner)
54+
const now = Date.now();
55+
56+
if (now - lastScanTime > 10000) {
57+
lastScannedQR = null;
58+
}
59+
60+
if (content.data && isReady && content.data !== lastScannedQR) {
61+
lastScannedQR = content.data;
62+
lastScanTime = now;
63+
SuperScan('{% url 'cool_meals_api' %}', content.data, scanner);
5664
isReady = false;
57-
setTimeout(()=>{
65+
setTimeout(() => {
5866
isReady = true;
59-
}, 1000)
67+
}, 1000);
68+
} else if (content.data === lastScannedQR) {
69+
console.log("Duplicate QR detected (ignored): " + content.data);
6070
}
61-
console.log("SCANED: "+ content.data);
71+
console.log("SCANNED: " + content.data);
6272
}, {
6373
popup: false,
64-
})
74+
});
6575
scanner.show();
66-
// $('.scan-again').click(() => {
67-
// scanner.show()
68-
// document.getElementById('no').style.display = "none";
69-
//document.getElementById('success').style.display = "none";
70-
// })
71-
})
76+
});
7277

73-
74-
// Core function to send data to meals API
7578
function SuperScan(theUrl, content, scanner) {
7679
$.ajax({
7780
type: "POST",
@@ -89,37 +92,22 @@ <h1 style="font-size: larger">{{ meal_name }}</h1>
8992
document.getElementById('deny').innerHTML = "Something went wrong.";
9093
}
9194

92-
if (data.success == true) { // 200 ok means can eat
93-
// show success alert
94-
95+
if (data.success == true) {
9596
$('#success').fadeOut("fast");
9697
$('#success').fadeIn("fast");
97-
// Display user's diet
98-
99-
// console.log(data.responseText.diet);
10098
document.getElementById('succ').innerHTML = "User allowed to entry <br> Diet: <b>" + data.diet + "</b>";
101-
// hide no pass alert
10299
document.getElementById('no').style.display = "none";
103-
104-
} else { // else, (may be 400). Has already eatern.
105-
// hide success alert
100+
} else {
106101
document.getElementById('success').style.display = "none";
107-
108102
document.getElementById('deny').innerHTML = data.error;
109-
// show no pass alert
110103
$('#no').fadeOut("fast");
111104
$('#no').fadeIn("fast");
112-
// hide alert after time desired.
113-
114105
}
115106
}
116-
117-
118107
});
119108
}
120-
121-
122109
</script>
110+
123111
</div>
124112

125113
</div>

organizers/templates/application_detail.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,21 @@ <h3>Personal</h3>
6767
{% include 'include/field.html' with desc='Under age (-18)' value=app.under_age|yesno:'Yes,No,Maybe' %}
6868
{% if app.resume %}
6969
<dt>Resume</dt>
70-
<dd><a href="{% url 'app_resume' app.uuid %}" target="_blank">{{ app.resume.name }}</a></dd>
70+
<dd>
71+
<a href="{% url 'app_resume' app.uuid %}" target="_blank">{{ app.resume.name }}</a>
72+
{% if app.cv_flagged %}
73+
<p class="text-danger" style="margin-top:5px;">CV already flagged for review</p>
74+
{% else %}
75+
<form action="" method="post" style="margin-top:8px;">
76+
{% csrf_token %}
77+
<input type="hidden" name="app_id" value="{{ app.pk }}"/>
78+
<button name="set_flagged_cv" class="btn btn-warning btn-sm btn-block" value="set_flagged_cv"
79+
onclick="return confirm('Are you sure you want set this application as Flagged?')">
80+
Flag CV for Review
81+
</button>
82+
</form>
83+
{% endif %}
84+
</dd>
7185

7286
{% endif %}
7387
<hr>

organizers/views.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,19 @@
7373

7474

7575
def add_vote(application, user, tech_rat, pers_rat):
76+
"""
77+
Save the vote of the application and
78+
if the number of votes is >= 5 and the CV is not flagged, create an AcceptedResume
79+
"""
7680
v = models.Vote()
7781
v.user = user
7882
v.application = application
7983
v.tech = tech_rat
8084
v.personal = pers_rat
8185
v.save()
86+
votes_count = application.vote_set.count()
87+
if votes_count >= 5 and not application.cv_flagged:
88+
AcceptedResume.objects.update_or_create(application=application, defaults={'accepted': True})
8289
return v
8390

8491

@@ -371,7 +378,11 @@ def post(self, request, *args, **kwargs):
371378
self.slack_invite(application)
372379
elif request.POST.get('set_dubious') and request.user.is_organizer:
373380
application.set_dubious(request.user, dubious_type, dubious_comment_text)
374-
elif request.POST.get('contact_user') and request.user.has_dubious_access:
381+
elif request.POST.get("set_flagged_cv") and request.user.is_organizer:
382+
application.set_flagged_cv()
383+
elif request.POST.get("unset_flagged_cv") and request.user.is_organizer:
384+
application.unset_flagged_cv()
385+
elif request.POST.get("contact_user") and request.user.has_dubious_access:
375386
application.set_contacted(request.user)
376387
elif request.POST.get("unset_dubious") and request.user.has_dubious_access:
377388
add_comment(
@@ -517,6 +528,10 @@ def post(self, request, *args, **kwargs):
517528
application.set_dubious(request.user, dubious_type, dubious_comment_text)
518529
elif request.POST.get("unset_dubious"):
519530
application.unset_dubious()
531+
elif request.POST.get("set_flagged_cv") and request.user.is_organizer:
532+
application.set_flagged_cv()
533+
elif request.POST.get("unset_flagged_cv") and request.user.is_organizer:
534+
application.unset_flagged_cv()
520535
elif request.POST.get("set_blacklist") and request.user.is_organizer:
521536
application.set_blacklist()
522537
elif (
@@ -618,6 +633,10 @@ def post(self, request, *args, **kwargs):
618633
application.set_dubious(request.user, dubious_type, dubious_comment_text)
619634
elif request.POST.get("unset_dubious"):
620635
application.unset_dubious()
636+
elif request.POST.get("set_flagged_cv") and request.user.is_organizer:
637+
application.set_flagged_cv()
638+
elif request.POST.get("unset_flagged_cv") and request.user.is_organizer:
639+
application.unset_flagged_cv()
621640
elif request.POST.get("set_blacklist") and request.user.is_organizer:
622641
application.set_blacklist()
623642
elif (
@@ -1060,7 +1079,7 @@ def get_context_data(self, **kwargs):
10601079
context = super().get_context_data(**kwargs)
10611080
app = (
10621081
models.HackerApplication.objects.filter(
1063-
acceptedresume__isnull=True, cvs_edition=True
1082+
acceptedresume__isnull=True, cv_flagged=True, resume__isnull=False
10641083
)
10651084
.exclude(status__in=[APP_DUBIOUS, APP_BLACKLISTED])
10661085
.first()

teams/forms.py

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,48 @@
66

77

88
class JoinTeamForm(forms.ModelForm):
9+
"""
10+
Form for joining an existing team.
11+
Validates that the team exists and that it is not full.
12+
If the team does not exist, it raises a validation error.
13+
If the team is full, it raises a validation error.
14+
"""
15+
916
def clean_team_code(self):
10-
team_code = self.cleaned_data['team_code']
17+
team_code = self.cleaned_data["team_code"]
1118
teammates = Team.objects.filter(team_code=team_code).count()
1219
if teammates == 0:
13-
raise forms.ValidationError("No team exists with the current code. Did you want to create a team instead?")
14-
max_teammates = getattr(settings, 'HACKATHON_MAX_TEAMMATES', 4)
20+
raise forms.ValidationError(
21+
"No team exists with the current code. Did you want to create a team instead?"
22+
)
23+
max_teammates = getattr(settings, "HACKATHON_MAX_TEAMMATES", 4)
1524
if teammates == max_teammates:
16-
raise forms.ValidationError("Full team. Max teammates is %d" % max_teammates)
17-
if Team.objects.filter(team_code=team_code).exclude(
18-
user__hackerapplication_application__status__in=[APP_PENDING, APP_LAST_REMIDER, APP_DUBIOUS]).exists():
19-
raise forms.ValidationError("Some members of this team are accepted or cancelled. You can't join their "
20-
"team here but don't worry you still can join them on the event.")
25+
raise forms.ValidationError(
26+
"Full team. Max teammates is %d" % max_teammates
27+
)
28+
if (
29+
Team.objects.filter(team_code=team_code)
30+
.exclude(
31+
user__hackerapplication_application__status__in=[
32+
APP_PENDING,
33+
APP_LAST_REMIDER,
34+
APP_DUBIOUS,
35+
]
36+
)
37+
.exists()
38+
):
39+
raise forms.ValidationError(
40+
"Some members of this team are accepted or cancelled. You can't join their "
41+
"team here but don't worry you still can join them on the event."
42+
)
2143
return team_code
2244

2345
class Meta:
2446
model = Team
25-
exclude = ['user', ]
26-
labels = {
27-
'team_code': 'Your team code'
28-
}
47+
exclude = [
48+
"user",
49+
]
50+
labels = {"team_code": "Your team code"}
2951
help_texts = {
30-
'team_code': 'Paste here the team code that your teammate has sent you'
52+
"team_code": "Paste here the team code that your teammate has sent you"
3153
}

0 commit comments

Comments
 (0)