Skip to content

Commit

Permalink
Start of work on hints
Browse files Browse the repository at this point in the history
  • Loading branch information
Dillon Lareau committed Jan 9, 2020
1 parent 3402a28 commit f4cc30f
Show file tree
Hide file tree
Showing 18 changed files with 705 additions and 14 deletions.
14 changes: 14 additions & 0 deletions huntserver/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@
# Register your models here.
from . import models


class UnlockableInline(admin.TabularInline):
model = models.Unlockable
extra = 1


class ResponseInline(admin.TabularInline):
model = models.Response
extra = 1
# template = "admin/tabular_custom.html"


class UnlockInline(admin.TabularInline):
model = models.Puzzle.unlocks.through
extra = 2
Expand All @@ -36,6 +39,7 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs):
pass
return super(UnlockInline, self).formfield_for_foreignkey(db_field, request, **kwargs)


class PuzzleAdmin(admin.ModelAdmin):
def get_object(self, request, object_id, to_field):
# Hook obj for use in formfield_for_manytomany
Expand All @@ -52,14 +56,17 @@ def formfield_for_manytomany(self, db_field, request, **kwargs):
'answer', 'extra_data', 'num_pages', 'num_required_to_unlock')
inlines = (UnlockInline, ResponseInline)


class PrepuzzleAdminForm(forms.ModelForm):
model = models.Prepuzzle

class Meta:
fields = '__all__'
widgets = {
'template': HtmlEditor(attrs={'style': 'width: 90%; height: 400px;'}),
}


class PrepuzzleAdmin(admin.ModelAdmin):
form = PrepuzzleAdminForm
readonly_fields = ('puzzle_url',)
Expand Down Expand Up @@ -114,22 +121,27 @@ def save(self, commit=True):

return team


class TeamAdmin(admin.ModelAdmin):
form = TeamAdminForm
list_filter = ('hunt',)


class PersonAdmin(admin.ModelAdmin):
list_display = ('__unicode__', 'is_shib_acct',)
search_fields = ['user__email', 'user__username', 'user__first_name', 'user__last_name']


class HuntAdminForm(forms.ModelForm):
model = models.Hunt

class Meta:
fields = '__all__'
widgets = {
'template': HtmlEditor(attrs={'style': 'width: 90%; height: 400px;'}),
}


class HuntAdmin(admin.ModelAdmin):
form = HuntAdminForm

Expand All @@ -141,6 +153,7 @@ class Meta:
verbose_name = User._meta.verbose_name
verbose_name_plural = User._meta.verbose_name_plural


class UserProxyAdmin(admin.ModelAdmin):
search_fields = ['email', 'username', 'first_name', 'last_name']

Expand All @@ -160,3 +173,4 @@ class UserProxyAdmin(admin.ModelAdmin):
admin.site.register(models.Message)
admin.site.register(models.Response)
admin.site.register(models.Unlockable)
admin.site.register(models.Hint)
35 changes: 26 additions & 9 deletions huntserver/forms.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
from django import forms
from .models import Person
from django.conf import settings
from django.contrib.auth.models import User
import re


class AnswerForm(forms.Form):
answer = forms.CharField(max_length=100, label='Answer')


class SubmissionForm(forms.Form):
response = forms.CharField(max_length=400, label='response', initial="Wrong Answer")
sub_id = forms.CharField(label='sub_id')


class UnlockForm(forms.Form):
team_id = forms.CharField(label='team_id')
puzzle_id = forms.CharField(label='puzzle_id')


class PersonForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PersonForm, self).__init__(*args, **kwargs)
Expand All @@ -26,6 +29,7 @@ class Meta:
model = Person
fields = ['phone', 'allergies']


class UserForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UserForm, self).__init__(*args, **kwargs)
Expand All @@ -46,8 +50,8 @@ def clean_email(self):

def clean_username(self):
username = self.cleaned_data.get('username')
if(re.match("^[a-zA-Z0-9]+([_-]?[a-zA-Z0-9])*$", username) == None):
raise forms.ValidationError("Username must contain only letters, digits, or '-' or '_' ")
if(re.match("^[a-zA-Z0-9]+([_-]?[a-zA-Z0-9])*$", username) is None):
raise forms.ValidationError("Username must contain only letters, digits, or '-' or '_'")
return username

def clean_confirm_password(self):
Expand All @@ -65,6 +69,7 @@ class Meta:
'username': "Required. 30 characters or fewer. Letters, digits and '-' or '_' only.",
}


class ShibUserForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ShibUserForm, self).__init__(*args, **kwargs)
Expand All @@ -75,9 +80,9 @@ def __init__(self, *args, **kwargs):
def clean_username(self):
instance = getattr(self, 'instance', None)
if instance and instance.id:
return instance.username
return instance.username
else:
return self.cleaned_data['username']
return self.cleaned_data['username']

def clean_email(self):
email = self.cleaned_data.get('email')
Expand All @@ -86,11 +91,23 @@ def clean_email(self):
raise forms.ValidationError('Someone is already using that email address.')
return email


class Meta:
model = User
model = User
fields = ['first_name', 'last_name', 'username', 'email']



class EmailForm(forms.Form):
subject = forms.CharField(label='Subject')
message = forms.CharField(label='Message', widget = forms.Textarea)
message = forms.CharField(label='Message', widget=forms.Textarea)


class HintRequestForm(forms.Form):
request = forms.CharField(max_length=400, label='Hint Request Text', widget=forms.Textarea,
help_text="Please describe your progress on the puzzle, and where you feel you are stuck. Max length 400 characters.")


class HintResponseForm(forms.Form):
response = forms.CharField(max_length=400, label='Hint Response Text',
widget=forms.Textarea(attrs={'rows': 5, 'cols': 30}),
help_text="Max length 400 characters.")
hint_id = forms.CharField(label='hint_id', widget=forms.HiddenInput())
74 changes: 72 additions & 2 deletions huntserver/hunt_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import os
import re

from .models import Puzzle, Hunt, Submission, Message, Unlockable, Prepuzzle
from .forms import AnswerForm
from .models import Puzzle, Hunt, Submission, Message, Unlockable, Prepuzzle, Hint
from .forms import AnswerForm, HintRequestForm
from .utils import respond_to_submission, team_from_user_hunt, dummy_team_from_hunt
from .info_views import current_hunt_info

Expand Down Expand Up @@ -279,6 +279,76 @@ def puzzle_view(request, puzzle_id):
return render(request, 'puzzle.html', context)


@login_required
def puzzle_hint(request, puzzle_id):
"""
A view to handle hint requests via POST, handle response update requests via AJAX, and
render the basic puzzle-hint pages.
"""
puzzle = get_object_or_404(Puzzle, puzzle_id__iexact=puzzle_id)
team = team_from_user_hunt(request.user, puzzle.hunt)

if request.method == 'POST':
# If the hunt isn't public and you aren't signed in, please stop...
if(team is None):
return HttpResponse('fail')

# Normal answer responses for a signed in user in an ongoing hunt
form = HintRequestForm(request.POST)
if form.is_valid():
h = Hint.objects.create(request=form.cleaned_data['request'], puzzle=puzzle, team=team,
request_time=timezone.now(), last_modified_time=timezone.now())

# Render response to HTML
hint_list = [render_to_string('hint_row.html', {'hint': h})]

try:
last_hint = Hint.objects.latest('last_modified_time')
last_date = last_hint.last_modified_time.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
except Hint.DoesNotExist:
last_date = timezone.now().strftime('%Y-%m-%dT%H:%M:%S.%fZ')

# Send back rendered response for display
context = {'hint_list': hint_list, 'last_date': last_date}
return HttpResponse(json.dumps(context))

# Will return HTML rows for all submissions the user does not yet have
elif request.is_ajax():
if(team is None):
return HttpResponseNotFound('access denied')

# Find which objects the user hasn't seen yet and render them to HTML
last_date = datetime.strptime(request.GET.get("last_date"), '%Y-%m-%dT%H:%M:%S.%fZ')
last_date = last_date.replace(tzinfo=tz.gettz('UTC'))
hints = Hint.objects.filter(last_modified_time__gt=last_date)
hints = hints.filter(team=team, puzzle=puzzle)
hint_list = [render_to_string('hint_row.html', {'hint': hint}) for hint in hints]

try:
last_hint = Hint.objects.latest('last_modified_time')
last_date = last_hint.last_modified_time.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
except Hint.DoesNotExist:
last_date = timezone.now().strftime('%Y-%m-%dT%H:%M:%S.%fZ')

context = {'hint_list': hint_list, 'last_date': last_date}
return HttpResponse(json.dumps(context))

else:
if(team is None or puzzle not in team.unlocked.all()):
return render(request, 'access_error.html', {'reason': "puzzle"})

form = HintRequestForm()
hints = team.hint_set.filter(puzzle=puzzle).order_by('pk')
try:
last_hint = Hint.objects.latest('last_modified_time')
last_date = last_hint.last_modified_time.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
except Hint.DoesNotExist:
last_date = timezone.now().strftime('%Y-%m-%dT%H:%M:%S.%fZ')
context = {'form': form, 'pages': list(range(puzzle.num_pages)), 'puzzle': puzzle,
'hint_list': hints, 'last_date': last_date, 'team': team}
return render(request, 'puzzle_hint.html', context)


@login_required
def chat(request):
"""
Expand Down
29 changes: 29 additions & 0 deletions huntserver/migrations/0038_hint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2020-01-07 19:37
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('huntserver', '0037_auto_20190925_1733'),
]

operations = [
migrations.CreateModel(
name='Hint',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('request', models.CharField(help_text=b'The text of the request for the hint', max_length=400)),
('request_time', models.DateTimeField(help_text=b'Hint request time')),
('response', models.CharField(help_text=b'The text of the response to the hint request', max_length=400)),
('response_time', models.DateTimeField(help_text=b'Hint request time')),
('last_modified_time', models.DateTimeField(help_text=b'Last time of modification')),
('puzzle', models.ForeignKey(help_text=b'The puzzle that this hint is related to', on_delete=django.db.models.deletion.CASCADE, to='huntserver.Puzzle')),
('team', models.ForeignKey(help_text=b'The team that requested the hint', on_delete=django.db.models.deletion.CASCADE, to='huntserver.Team')),
],
),
]
25 changes: 25 additions & 0 deletions huntserver/migrations/0039_auto_20200108_1124.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2020-01-08 16:24
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('huntserver', '0038_hint'),
]

operations = [
migrations.AlterField(
model_name='hint',
name='response',
field=models.CharField(blank=True, help_text=b'The text of the response to the hint request', max_length=400),
),
migrations.AlterField(
model_name='hint',
name='response_time',
field=models.DateTimeField(help_text=b'Hint request time', null=True),
),
]
20 changes: 20 additions & 0 deletions huntserver/migrations/0040_auto_20200108_1126.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2020-01-08 16:26
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('huntserver', '0039_auto_20200108_1124'),
]

operations = [
migrations.AlterField(
model_name='hint',
name='response_time',
field=models.DateTimeField(blank=True, help_text=b'Hint response time', null=True),
),
]
23 changes: 23 additions & 0 deletions huntserver/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,29 @@ def __str__(self):
return self.regex + " => " + self.text


@python_2_unicode_compatible
class Hint(models.Model):
""" A class to represent a hint to a puzzle """

puzzle = models.ForeignKey(Puzzle, on_delete=models.CASCADE,
help_text="The puzzle that this hint is related to")
team = models.ForeignKey(Team, on_delete=models.CASCADE,
help_text="The team that requested the hint")
request = models.CharField(max_length=400,
help_text="The text of the request for the hint")
request_time = models.DateTimeField(
help_text="Hint request time")
response = models.CharField(max_length=400, blank=True,
help_text="The text of the response to the hint request")
response_time = models.DateTimeField(null=True, blank=True,
help_text="Hint response time")
last_modified_time = models.DateTimeField(
help_text="Last time of modification")

def __str__(self):
return self.team.team_name + ": " + self.puzzle.puzzle_name + " (" + str(self.request_time) + ")"


class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name):
# If the filename already exists, remove it as if it was a true file system
Expand Down

0 comments on commit f4cc30f

Please sign in to comment.