Skip to content

Commit

Permalink
Added permission slip to under age (#116)
Browse files Browse the repository at this point in the history
* Added permission slip to under age

* Add CI of python 3.10
  • Loading branch information
Casassarnau authored Oct 28, 2023
1 parent 7ca431b commit e978b19
Show file tree
Hide file tree
Showing 32 changed files with 589 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/django.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: ['3.8', '3.9']
python-version: ['3.8', '3.9', '3.10']

steps:
- uses: actions/checkout@v3
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
FROM python:3.9.13
RUN apt-get update
RUN apt-get install -y cron && touch /var/log/cron.log
RUN apt-get install texlive-latex-extra -y
RUN pip install --upgrade pip
WORKDIR /code

Expand Down
7 changes: 7 additions & 0 deletions app/hackathon_variables.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
HACKATHON_NAME = 'HackUPC'
HACKATHON_DESCRIPTION = 'Join us for BarcelonaTech\'s hackathon. 36h.'
HACKATHON_ORG = 'Hackers@UPC'
HACKATHON_START_DATE = '12/12/2012'
HACKATHON_END_DATE = '14/12/2012'
HACKATHON_LOCATION = 'Barcelona'

HACKATHON_CONTACT_EMAIL = '[email protected]'
HACKATHON_SOCIALS = {'Facebook': ('https://www.facebook.com/hackupc', 'bi-facebook'),
Expand All @@ -22,4 +25,8 @@
SUPPORTED_RESUME_EXTENSIONS = ['.pdf']
FRIENDS_MAX_CAPACITY = None

REQUIRE_PERMISSION_SLIP_TO_UNDER_AGE = True
SUPPORTED_PERMISSION_SLIP_EXTENSIONS = ['.pdf']
PARTICIPANT_CAN_UPLOAD_PERMISSION_SLIP = True

ATTRITION_RATE = 1.5
21 changes: 20 additions & 1 deletion app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
'django_jwt',
'django_jwt.server',
'django_bootstrap5',
'django_tex',
'compressor',
'colorfield',
'corsheaders',
Expand Down Expand Up @@ -102,7 +103,6 @@
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.request',
'app.template.app_variables',
'csp.context_processors.nonce',
],
Expand All @@ -112,6 +112,20 @@
},
},
},
{
'NAME': 'tex',
'BACKEND': 'django_tex.engine.TeXEngine',
'DIRS': [BASE_DIR / 'app' / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'app.template.app_variables',
]
},
},
]

WSGI_APPLICATION = 'app.wsgi.application'
Expand Down Expand Up @@ -444,6 +458,11 @@
# DateTime formats
USE_L10N = False
DATETIME_FORMAT = 'N j, Y, H:i'
DATE_FORMAT = 'N j, Y'
SHORT_DATETIME_FORMAT = 'Y/m/d H:i'
TIME_FORMAT = 'H:i:s'
SHORT_DATE_FORMAT = 'Y/m/d'

# Latex binary
LATEX_INTERPRETER = 'pdflatex'
LATEX_GRAPHICSPATH = [os.path.join(BASE_DIR, 'latex_graphics')]
4 changes: 4 additions & 0 deletions app/static/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,7 @@ footer {
.g-recaptcha {
display: inline-block;
}

.a-none:link, .a-none:visited, .a-none:hover, .a-none:active, .a-none {
text-decoration: none !important;
}
12 changes: 12 additions & 0 deletions app/template.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.conf import settings
from django.urls import reverse
from django.utils import timezone

from app.utils import get_theme, is_installed
from application.models import ApplicationTypeConfig
Expand Down Expand Up @@ -48,6 +49,13 @@ def get_main_nav(request):
return nav


def get_date(text):
try:
return timezone.datetime.strptime(text, '%d/%m/%Y')
except ValueError:
return None


def app_variables(request):
return {
'main_nav': get_main_nav(request),
Expand All @@ -64,4 +72,8 @@ def app_variables(request):
'socialaccount_providers': getattr(settings, 'SOCIALACCOUNT_PROVIDERS', {}),
'auth_password_validators': getattr(settings, 'PASSWORD_VALIDATORS', {}),
'tables_export_supported': getattr(settings, 'DJANGO_TABLES2_EXPORT_FORMATS', []),
'participant_can_upload_permission_slip': getattr(settings, 'PARTICIPANT_CAN_UPLOAD_PERMISSION_SLIP', False),
'hack_start_date': get_date(getattr(settings, 'HACKATHON_START_DATE', '')),
'hack_end_date': get_date(getattr(settings, 'HACKATHON_END_DATE', '')),
'hack_location': getattr(settings, 'HACKATHON_LOCATION', ''),
}
1 change: 1 addition & 0 deletions app/templates/mails/components/button.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
</tr>
</tbody>
</table>
<p>If the link does not work: <a>{{ url }}</a></p>
14 changes: 14 additions & 0 deletions app/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from django.shortcuts import redirect, render
from django.views import View
from django_tex.shortcuts import render_to_pdf

from app.template import app_variables
from user.mixins import LoginRequiredMixin


Expand Down Expand Up @@ -40,3 +42,15 @@ def handler_error_403(request, exception=None, **kwargs):

def handler_error_400(request, exception=None, **kwargs):
return render(request=request, template_name='errors/400.html', context={'exception': exception}, status=400)


class LatexTemplateView(View):
template_name = ''
file_name = ''

def get_context_data(self, **kwargs):
return app_variables(self.request)

def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return render_to_pdf(request, self.template_name, context, filename=self.file_name)
1 change: 1 addition & 0 deletions application/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ class PromotionalCodeAdmin(admin.ModelAdmin):
admin.site.register(models.ApplicationTypeConfig, ApplicationTypeConfigAdmin)
admin.site.register(models.ApplicationLog)
admin.site.register(models.Edition)
admin.site.register(models.PermissionSlip)
admin.site.register(models.PromotionalCode, PromotionalCodeAdmin)
22 changes: 22 additions & 0 deletions application/emails.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.urls import reverse

from app.emails import Email
Expand Down Expand Up @@ -32,3 +33,24 @@ def get_email_expired(application):
'app_contact': getattr(settings, 'HACKATHON_CONTACT_EMAIL', ''),
}
return Email(name='application_expired', context=context, to=application.user.email)


def send_email_permission_slip_upload(request, application):
url = request.build_absolute_uri(reverse('permission_slip', kwargs={'uuid': application.get_uuid}))
context = {
'user': application.user,
'url': url,
}
permission_slip_managers = (get_user_model().objects.with_perm('application.can_review_permission_slip')
.value_list('email', flat=True))
Email(name='permission_slip_upload', context=context, to=permission_slip_managers, request=request).send()


def send_email_permission_slip_review(request, application, permission_slip):
url = request.build_absolute_uri(reverse('permission_slip', kwargs={'uuid': application.get_uuid}))
context = {
'user': application.user,
'permission_slip': permission_slip,
'url': url,
}
Email(name='permission_slip_review', context=context, to=application.user.email, request=request).send()
30 changes: 30 additions & 0 deletions application/migrations/0032_permissionslip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 4.2.2 on 2023-09-11 12:05

import application.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('application', '0031_alter_applicationtypeconfig_spots'),
]

operations = [
migrations.CreateModel(
name='PermissionSlip',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('N', 'Missing document'), ('D', 'Not accepted'), ('U', 'On review'), ('A', 'Accepted')], default='N', max_length=2)),
('file', models.FileField(null=True, upload_to=application.models.get_permission_slip_file_name)),
('edition', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='application.edition')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'permissions': (('can_review_permission_slip', 'Can review permission slip'),),
},
),
]
62 changes: 62 additions & 0 deletions application/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ def exclude(self, *args, **kwargs):
kwargs = self.convert_kwargs(kwargs)
return super().exclude(*args, **kwargs)

def invited(self):
return self.filter(status__in=[self.model.STATUS_INVITED, self.model.STATUS_LAST_REMINDER,
self.model.STATUS_CONFIRMED, self.model.STATUS_ATTENDED])


class Application(models.Model):
STATUS_PENDING = 'P'
Expand Down Expand Up @@ -343,6 +347,15 @@ def save(self, *args, **kwargs):
self.last_modified = timezone.now()
super().save(*args, **kwargs)

def get_permission_slip(self, raise_404=False):
try:
return PermissionSlip.objects.get(user_id=self.user_id, edition_id=self.edition_id)
except PermissionSlip.DoesNotExist:
if raise_404:
from django.http import Http404
raise Http404
return None

class Meta:
unique_together = ('type', 'user', 'edition')
permissions = (
Expand Down Expand Up @@ -437,3 +450,52 @@ def form_data(self, new_data: dict):
data = self.form_data
data.update(new_data)
self.data = json.dumps(data)


def get_permission_slip_file_name(instance, filename):
return '%s/User/permission_slip/%s_%s.%s' % (instance.edition.name, instance.user.get_full_name().replace(' ', '-'),
instance.user.id, filename.split('.')[-1])


class PermissionSlip(models.Model):
STATUS_ACCEPTED = 'A'
STATUS_UPLOADED = 'U'
STATUS_NONE = 'N'
STATUS_DENIED = 'D'
STATUS = (
(STATUS_NONE, _('Missing document')),
(STATUS_DENIED, _('Not accepted')),
(STATUS_UPLOADED, _('On review')),
(STATUS_ACCEPTED, _('Accepted')),
)
STATUS_DESCRIPTION = {
STATUS_NONE: _('Upload the permission slip signed by your parents or legal tutors'),
STATUS_DENIED: _('The document has been reviewed and has some problem'),
STATUS_UPLOADED: _('Document uploaded successfully, now we will review it'),
STATUS_ACCEPTED: _('The document has been accepted!'),
}
STATUS_COLORS = {
STATUS_NONE: 'danger',
STATUS_DENIED: 'warning',
STATUS_UPLOADED: 'info',
STATUS_ACCEPTED: 'success',
}

user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
edition = models.ForeignKey(Edition, null=True, on_delete=models.SET_NULL)
status = models.CharField(choices=STATUS, max_length=2, default=STATUS_NONE)
file = models.FileField(upload_to=get_permission_slip_file_name, null=True)

def __str__(self):
return '%s - %s' % (self.edition.name, self.user.get_full_name())

def get_status_color(self):
return self.STATUS_COLORS.get(self.status)

def get_status_description(self):
return self.STATUS_DESCRIPTION.get(self.status)

class Meta:
permissions = (
('can_review_permission_slip', _('Can review permission slip')),
)
44 changes: 44 additions & 0 deletions application/other_forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import os

from django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _

from app.mixins import BootstrapFormMixin
from application.models import PermissionSlip
from application.validators import validate_file_extension

EXTENSIONS = getattr(settings, 'SUPPORTED_PERMISSION_SLIP_EXTENSIONS', None)


class PermissionSlipForm(forms.ModelForm, BootstrapFormMixin):
bootstrap_field_info = {'': {'fields': [{'name': 'file', 'space': 12}, {'name': 'terms', 'space': 12}]}}

file = forms.FileField(validators=[validate_file_extension(EXTENSIONS)], required=True,
label=_('Upload your permission slip'),
help_text=_('Accepted file formats: %s' % (', '.join(EXTENSIONS) if EXTENSIONS else 'Any')))
terms = forms.BooleanField(label=_('I understand that the permission slip I am providing will be used for '
'ensuring my safety during the event and for addressing any legal aspects '
'related to my participation in the hackathon. I hereby consent to the '
'use of this document for these purposes.'), required=True)

def __init__(self, *args, **kwargs):
instance = kwargs.get('instance', None)
self._old_file = instance.file if instance is not None else None
super().__init__(*args, **kwargs)

def save(self, commit=True):
instance = super().save(commit=False)
instance.status = instance.STATUS_UPLOADED
old_file = getattr(self, '_old_file', None)
try:
os.remove(old_file.path)
except ValueError:
pass
if commit:
instance.save()
return instance

class Meta:
model = PermissionSlip
fields = ('file', 'terms')
22 changes: 22 additions & 0 deletions application/templates/application_home/user_applications.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ <h5 class="modal-title" id="exampleModalLabel">{% translate 'QR code' %}</h5>
{% endwith %}
{% with applications_confirmed=user_applications_grouped|get_item:'C' %}
{% if applications_confirmed %}
{% if request.user.under_age_document_required and permission_slip_action %}
<div class="mt-2 rounded-3 p-3 bg-contrast">
<h2 class="text-center">{% translate 'Permission slip required for attendance' %}</h2>
<p class="text-center">Action needed to attend to the event!</p>
<div class="row justify-content-around mt-3 mb-3">
<div class="col-12 col-lg-6 d-grid d-md-block">
<a href="{% url 'permission_slip' applications_confirmed.0.get_uuid %}" class="btn btn-info col-12">{% trans 'Manage my permission slip' %}</a>
</div>
</div>
</div>
{% endif %}
<div class="mt-2 rounded-3 p-3 bg-contrast">
<div class="row">
<div class="col-12 col-lg-8">
Expand Down Expand Up @@ -91,6 +102,17 @@ <h2>{% translate 'Confirmed applications:' %} {{ applications_confirmed|get_type
{% endif %}
{% endwith %}
{% for app in user_applications_grouped|get_item:'default' %}
{% if request.user.under_age_document_required and permission_slip_action %}
<div class="mt-2 rounded-3 p-3 bg-contrast">
<h2 class="text-center">{% translate 'Permission slip required for attendance' %}</h2>
<p class="text-center">Action needed to attend to the event!</p>
<div class="row justify-content-around mt-3 mb-3">
<div class="col-12 col-lg-6 d-grid d-md-block">
<a href="{% url 'permission_slip' app.get_uuid %}" class="btn btn-info col-12">{% trans 'Manage my permission slip' %}</a>
</div>
</div>
</div>
{% endif %}
<div class="mt-2 rounded-3 p-3 bg-contrast">
<div class="row">
<div class="col-12 col-md-9"><h2>{{ app.type.name }} {% translate 'application' %}</h2></div>
Expand Down
16 changes: 16 additions & 0 deletions application/templates/mails/permission_slip_review.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends 'mails/base.html' %}
{% block content %}
<p>Hi {{ user.first_name }},</p>


{% if permission_slip.status == permission_slip.STATUS_ACCEPTED %}
<p>Your permission slip has just been reviewed and has been accepted!</p>
{% else %}
<p>Your permission slip has some problem and has been denied. Check that everything on the permission slip is ok and sent it again. Remember it has to be signed by one of your parents or legal tutors</p>

{% include 'mails/components/button.html' with url=url text='Check permission slip' %}
{% endif %}

<p>Best regards,</p>
<p>{{ app_name }}.</p>
{% endblock %}
Loading

0 comments on commit e978b19

Please sign in to comment.