Skip to content

Commit c9ce0fd

Browse files
authored
Merge pull request #99 from HackAssistant/draft_applis
Added draft applications
2 parents d4c8768 + 0a97712 commit c9ce0fd

File tree

11 files changed

+203
-23
lines changed

11 files changed

+203
-23
lines changed

app/static/js/toasts.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
let ToastManager = {
2+
add_toast: function (message, type, delay=null) {
3+
let toast = $(
4+
`<div class="toast align-items-center fade show bg-${type}" role="alert" aria-live="assertive" aria-atomic="true">
5+
<div class="d-flex">
6+
<div class="toast-body">${message}</div>
7+
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
8+
</div>
9+
</div>`
10+
)
11+
$('#toasts').append(toast)
12+
if (delay !== null) {
13+
setTimeout(this.clear, delay, toast.get(0));
14+
}
15+
},
16+
success: function (message, delay=null) {
17+
this.add_toast(message, 'success', delay)
18+
},
19+
info: function (message, delay=null) {
20+
this.add_toast(message, 'info', delay)
21+
},
22+
warning: function (message, delay=null) {
23+
this.add_toast(message, 'warning', delay)
24+
},
25+
error: function (message, delay=null) {
26+
this.add_toast(message, 'danger', delay)
27+
},
28+
primary: function (message, delay=null) {
29+
this.add_toast(message, 'primary', delay)
30+
},
31+
secondary: function (message, delay=null) {
32+
this.add_toast(message, 'secondary', delay)
33+
},
34+
clear: function (item) {
35+
bootstrap.Toast.getOrCreateInstance(item).hide()
36+
},
37+
clearAll: function () {
38+
$('#toasts > div').each((_, item) => {
39+
this.clear(item)
40+
})
41+
}
42+
}

app/templates/components/bootstrap5_form.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
field.fadeIn()
108108
} else {
109109
field.fadeOut()
110+
field.find('input,textarea,select').val('')
110111
}
111112
})
112113
if (!{{ values|safe }}.includes(objectifyForm(form)['{{ cond_field_name }}'])) {

app/templates/components/imports.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@
2828

2929
<!-- Our WebPage style -->
3030
<link href="{% static 'css/main.css' %}" rel="stylesheet">
31+
32+
<!-- ToastManager -->
33+
<script src="{% static 'js/toasts.js' %}"></script>

app/templates/components/toast.html

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
{% if messages %}
2-
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 11">
3-
{% for message in messages %}
4-
<div class="toast align-items-center show bg-{{ message.tags }}" role="alert" aria-live="assertive" aria-atomic="true">
5-
<div class="d-flex">
6-
<div class="toast-body">{{ message|capfirst }}</div>
7-
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
8-
</div>
1+
<div id="toasts" class="position-fixed bottom-0 end-0 p-3" style="z-index: 11">
2+
{% for message in messages %}
3+
<div class="toast align-items-center fade show bg-{{ message.tags }}" role="alert" aria-live="assertive" aria-atomic="true">
4+
<div class="d-flex">
5+
<div class="toast-body">{{ message|capfirst }}</div>
6+
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
97
</div>
10-
{% endfor %}
11-
</div>
12-
{% endif %}
8+
</div>
9+
{% endfor %}
10+
</div>

application/forms/mentor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class MentorForm(ApplicationForm):
3232
study_work = forms.TypedChoiceField(
3333
required=True,
3434
label='Are you studying or working?',
35-
choices=(('Study', _('Study')), ('Study', _('Work'))),
35+
choices=(('Study', _('Study')), ('Work', _('Work'))),
3636
widget=forms.RadioSelect(attrs={'class': 'inline'})
3737
)
3838

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Generated by Django 4.0.8 on 2022-12-29 12:07
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
('application', '0029_applicationtypeconfig_spots'),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='DraftApplication',
18+
fields=[
19+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20+
('data', models.TextField(blank=True)),
21+
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)),
22+
],
23+
),
24+
]

application/models.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,3 +394,32 @@ def create_log(cls, application, user, name='Change', comment=''):
394394
changes[field] = change
395395
log.changes = changes
396396
return log
397+
398+
399+
class DraftApplicationManager(models.QuerySet):
400+
def get_or_create(self, defaults=None, **kwargs):
401+
if isinstance(defaults, dict) and defaults.get('form_data', None) is not None:
402+
defaults['data'] = json.dumps(defaults['form_data'])
403+
del defaults['form_data']
404+
return super().get_or_create(defaults, **kwargs)
405+
406+
407+
class DraftApplication(models.Model):
408+
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING)
409+
410+
data = models.TextField(blank=True)
411+
412+
objects = DraftApplicationManager.as_manager()
413+
414+
@property
415+
def form_data(self):
416+
try:
417+
return json.loads(self.data)
418+
except json.JSONDecodeError:
419+
return {}
420+
421+
@form_data.setter
422+
def form_data(self, new_data: dict):
423+
data = self.form_data
424+
data.update(new_data)
425+
self.data = json.dumps(data)

application/signals.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
from django.db.models.signals import post_save, post_delete
44
from django.dispatch import receiver
55

6-
from application.models import Edition, ApplicationTypeConfig, PromotionalCode
6+
from application.models import Edition, ApplicationTypeConfig, PromotionalCode, Application, DraftApplication
77

88

99
@receiver(post_delete, sender=Edition, weak=False)
1010
@receiver(post_save, sender=Edition, weak=False)
1111
def clear_groups(sender, instance, **kwargs):
1212
created = kwargs.get('created', None)
1313
if created is None or created:
14+
DraftApplication.objects.all().delete()
1415
for application_type in ApplicationTypeConfig.objects.all().values_list('name', flat=True):
1516
group = Group.objects.get(name=application_type)
1617
group.user_set.clear()
@@ -30,3 +31,9 @@ def clear_file_fields(sender, instance, **kwargs):
3031
@receiver(post_save, sender=PromotionalCode, weak=False)
3132
def reload_active(sender, instance, **kwargs):
3233
sender.active(force_update=True)
34+
35+
36+
@receiver(post_save, sender=Application, weak=False)
37+
def delete_draft_on_apply(sender, instance, created, **kwargs):
38+
if created:
39+
DraftApplication.objects.filter(user_id=instance.user_id).delete()

application/templates/application_form.html

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,53 @@ <h3 class="mb-1">{% if request.user.is_organizer %}{{ application_form.instance.
2828
<div class="alert alert-info alert-dismissible fade show" role="alert">
2929
{{ application_type.name }} {% translate 'applications are open. Hurry, they will close in' %} {{ application_type.time_left }}.
3030
</div>
31+
{% if request.user.is_authenticated %}
32+
<script>
33+
$(document).ready(() => {
34+
let timer
35+
let clear_timer
36+
let timeout = 5000; // Timout duration
37+
$('input,textarea,select').bind('change keyup', function () {
38+
if (timer) {
39+
clearTimeout(timer);
40+
}
41+
timer = setTimeout(saveData, timeout);
42+
});
43+
44+
// Save data
45+
function saveData() {
46+
if (clear_timer) {
47+
clearTimeout(clear_timer);
48+
}
49+
$('.saved-string').html('Saving...');
50+
let form_obj = {};
51+
let form_array = $('form').serializeArray();
52+
$.each(form_array, function () {
53+
if (form_obj[this.name]) {
54+
if (!form_obj[this.name].push) {
55+
form_obj[this.name] = [form_obj[this.name]];
56+
}
57+
form_obj[this.name].push(this.value || '');
58+
} else {
59+
form_obj[this.name] = this.value || '';
60+
}
61+
});
62+
63+
form_obj['csrfmiddlewaretoken'] = '{{ csrf_token }}'
64+
65+
// AJAX request
66+
$.ajax({
67+
url: '{% url 'save_draft' %}',
68+
type: 'post',
69+
data: form_obj,
70+
success: function () {
71+
ToastManager.info('Saved!', timeout)
72+
}
73+
});
74+
}
75+
})
76+
</script>
77+
{% endif %}
3178
{% endif %}
3279

3380
<form method="post" enctype="multipart/form-data" class="mt-4">

application/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@
1111
path('file/<str:field>/<str:uuid>/preview/', views.ApplicationFilePreview.as_view(),
1212
name='application_file_preview'),
1313
path('file/<str:field>/<str:uuid>/', views.ApplicationFile.as_view(), name='application_file'),
14+
path('apply/save_draft/', views.SaveDraftApplication.as_view(), name='save_draft'),
1415
]

0 commit comments

Comments
 (0)