From 6f7bb4652ad58775275eafa487cfd6e50fe3815d Mon Sep 17 00:00:00 2001 From: enric4000 Date: Fri, 6 Feb 2026 21:17:32 +0100 Subject: [PATCH 01/13] Edited Hackathon variables to be up to date with server, opened mentor DeadLine and fixed html header --- app/hackathon_variables.py | 105 +++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 45 deletions(-) diff --git a/app/hackathon_variables.py b/app/hackathon_variables.py index 493b10f8b..66f089726 100644 --- a/app/hackathon_variables.py +++ b/app/hackathon_variables.py @@ -4,61 +4,72 @@ from django.utils import timezone -HACKATHON_NAME = 'HackUPC' +HACKATHON_NAME = "HackUPC" # What's the name for the application -HACKATHON_APPLICATION_NAME = 'My HackUPC' +HACKATHON_APPLICATION_NAME = "My HackUPC" # Hackathon timezone -TIME_ZONE = 'CET' +TIME_ZONE = "CET" # This description will be used on the html and sharing meta tags -HACKATHON_DESCRIPTION = 'Join us for BarcelonaTech\'s hackathon. 36h. May 3 - 5.' +HACKATHON_DESCRIPTION = "Join us for BarcelonaTech's hackathon. 36h. April 24 - 26." # Domain where application is deployed, can be set by env variable -HACKATHON_DOMAIN = os.environ.get('DOMAIN', None) -HEROKU_APP_NAME = os.environ.get('HEROKU_APP_NAME', None) +HACKATHON_DOMAIN = os.environ.get("DOMAIN", None) +HEROKU_APP_NAME = os.environ.get("HEROKU_APP_NAME", None) if HEROKU_APP_NAME and not HACKATHON_DOMAIN: - HACKATHON_DOMAIN = '%s.herokuapp.com' % HEROKU_APP_NAME + HACKATHON_DOMAIN = "%s.herokuapp.com" % HEROKU_APP_NAME elif not HACKATHON_DOMAIN: - HACKATHON_DOMAIN = 'localhost:8000' + HACKATHON_DOMAIN = "localhost:8000" # Hackathon contact email: where should all hackers contact you. It will also be used as a sender for all emails -HACKATHON_CONTACT_EMAIL = 'contact@hackupc.com' +HACKATHON_CONTACT_EMAIL = "contact@hackupc.com" # Hackathon logo url, will be used on all emails -HACKATHON_LOGO_URL = 'https://my.hackupc.com/static/logo.png' +HACKATHON_LOGO_URL = "https://my.hackupc.com/static/logo.png" -HACKATHON_OG_IMAGE = 'https://hackupc.com/ogimage.png?v=2021' +HACKATHON_OG_IMAGE = "https://hackupc.com/ogimage.png?v=2021" # (OPTIONAL) Track visits on your website -HACKATHON_GOOGLE_ANALYTICS = 'UA-69542332-2' +HACKATHON_GOOGLE_ANALYTICS = "UA-69542332-2" # (OPTIONAL) Hackathon Twitter user -HACKATHON_TWITTER_ACCOUNT = 'hackupc' +HACKATHON_TWITTER_ACCOUNT = "hackupc" # (OPTIONAL) Hackathon Facebook page -HACKATHON_FACEBOOK_PAGE = 'hackupc' +HACKATHON_FACEBOOK_PAGE = "hackupc" # (OPTIONAL) Hackathon YouTube channel -HACKATHON_YOUTUBE_PAGE = 'UCiiRorGg59Xd5Sjj9bjIt-g' +HACKATHON_YOUTUBE_PAGE = "UCiiRorGg59Xd5Sjj9bjIt-g" # (OPTIONAL) Hackathon Instagram user -HACKATHON_INSTAGRAM_ACCOUNT = 'hackupc' +HACKATHON_INSTAGRAM_ACCOUNT = "hackupc" # (OPTIONAL) Hackathon Medium user -HACKATHON_MEDIUM_ACCOUNT = 'hackupc' +HACKATHON_MEDIUM_ACCOUNT = "hackupc" # (OPTIONAL) Github Repo for this project (so meta) -HACKATHON_GITHUB_REPO = 'https://github.com/hackupc/myhackupc/' +HACKATHON_GITHUB_REPO = "https://github.com/hackupc/myhackupc/" # (OPTIONAL) Applications deadline -HACKATHON_APP_DEADLINE = timezone.datetime(2030, 4, 24, 23, 59, tzinfo=timezone.pytz.timezone(TIME_ZONE)) -VOLUNTEER_APP_DEADLINE = timezone.datetime(2030, 4, 18, 23, 59, tzinfo=timezone.pytz.timezone(TIME_ZONE)) -MENTOR_APP_DEADLINE = timezone.datetime(2030, 3, 25, 23, 59, tzinfo=timezone.pytz.timezone(TIME_ZONE)) +HACKATHON_APP_DEADLINE = timezone.datetime( + 2026, 4, 1, 23, 59, tzinfo=timezone.pytz.timezone(TIME_ZONE) +) +VOLUNTEER_APP_DEADLINE = timezone.datetime( + 2026, 3, 27, 23, 59, tzinfo=timezone.pytz.timezone(TIME_ZONE) +) +MENTOR_APP_DEADLINE = timezone.datetime( + 2026, 3, 27, 23, 59, tzinfo=timezone.pytz.timezone(TIME_ZONE) +) + # (OPTIONAL) Online checkin activated -ONLINE_CHECKIN = timezone.datetime(2020, 5, 3, 17, 00, tzinfo=timezone.pytz.timezone(TIME_ZONE)) +ONLINE_CHECKIN = timezone.datetime( + 2020, 5, 3, 17, 00, tzinfo=timezone.pytz.timezone(TIME_ZONE) +) # (OPTIONAL) When to arrive at the hackathon -HACKATHON_ARRIVE = '' +HACKATHON_ARRIVE = "" # (OPTIONAL) When to arrive at the hackathon -HACKATHON_LEAVE = '' +HACKATHON_LEAVE = "" # (OPTIONAL) Hackathon live page -HACKATHON_LIVE_PAGE = 'https://live.hackupc.com' +HACKATHON_LIVE_PAGE = "https://live.hackupc.com" # (OPTIONAL) Regex to automatically match organizers emails and set them as organizers when signing up -REGEX_HACKATHON_ORGANIZER_EMAIL = '^.*@hackupc\.com$' +REGEX_HACKATHON_ORGANIZER_EMAIL = "^.*@hackupc\.com$" # (OPTIONAL) Send 500 errors to email while on production mode -HACKATHON_DEV_EMAILS = ['devs@hackupc.com', ] +HACKATHON_DEV_EMAILS = [ + "devs@hackupc.com", +] # Baggage configuration BAGGAGE_ENABLED = True @@ -67,10 +78,14 @@ # Reimbursement configuration REIMBURSEMENT_ENABLED = True DEFAULT_REIMBURSEMENT_AMOUNT = 100 -CURRENCY = '€' -REIMBURSEMENT_EXPIRY_DATE = timezone.datetime(2025, 5, 2, 17, 00, tzinfo=timezone.pytz.timezone(TIME_ZONE)) -REIMBURSEMENT_REQUIREMENTS = 'You have to submit a project and demo it during the event in order to get reimbursed' -REIMBURSEMENT_DEADLINE = timezone.datetime(2025, 5, 5, 23, 59, tzinfo=timezone.pytz.timezone(TIME_ZONE)) +CURRENCY = "€" +REIMBURSEMENT_EXPIRY_DATE = timezone.datetime( + 2025, 5, 2, 17, 00, tzinfo=timezone.pytz.timezone(TIME_ZONE) +) +REIMBURSEMENT_REQUIREMENTS = "You have to submit a project and demo it during the event in order to get reimbursed" +REIMBURSEMENT_DEADLINE = timezone.datetime( + 2025, 5, 5, 23, 59, tzinfo=timezone.pytz.timezone(TIME_ZONE) +) # (OPTIONAL) Max team members. Defaults to 4 TEAMS_ENABLED = True @@ -82,9 +97,9 @@ # (OPTIONAL) Slack credentials # Highly recommended to create a separate user account to extract the token from SLACK = { - 'team': os.environ.get('SL_TEAM', 'test'), + "team": os.environ.get("SL_TEAM", "test"), # Get it here: https://api.slack.com/custom-integrations/legacy-tokens - 'token': os.environ.get('SL_TOKEN', None) + "token": os.environ.get("SL_TOKEN", None), } # (OPTIONAL) Logged in cookie @@ -95,16 +110,16 @@ # Hardware configuration # Hardware request time length (in minutes) HARDWARE_ENABLED = True -#Hardware request time length (in minutes) +# Hardware request time length (in minutes) HARDWARE_REQUEST_TIME = 15 SLACK_BOT = { - 'id': os.environ.get('SL_BOT_ID', None), - 'token': os.environ.get('SL_BOT_TOKEN', None), - 'channel': os.environ.get('SL_BOT_CHANNEL', None), - 'director1': os.environ.get('SL_BOT_DIRECTOR1', None), - 'director2': os.environ.get('SL_BOT_DIRECTOR2', None) + "id": os.environ.get("SL_BOT_ID", None), + "token": os.environ.get("SL_BOT_TOKEN", None), + "channel": os.environ.get("SL_BOT_CHANNEL", None), + "director1": os.environ.get("SL_BOT_DIRECTOR1", None), + "director2": os.environ.get("SL_BOT_DIRECTOR2", None), } # Enable judging tab JUDGING_ENABLED = False @@ -119,7 +134,7 @@ # Enable blacklist separate pipeline (disabled by default) BLACKLIST_ENABLED = True -SUPPORTED_RESUME_EXTENSIONS = ['.pdf'] +SUPPORTED_RESUME_EXTENSIONS = [".pdf"] # Mentor/Volunteer applications can expire if they are invited, set to False to not MENTOR_EXPIRES = False @@ -129,9 +144,9 @@ HYBRID_HACKATHON = False N_MAX_LIVE_HACKERS = 600 -SERVER_EMAIL = 'HackUPC Team ' +SERVER_EMAIL = "HackUPC Team " -CODE_CONDUCT_LINK = 'https://legal.hackersatupc.org/hackupc/code_of_conduct' -LEGAL_LINK = 'https://legal.hackersatupc.org/hackupc/legal_notice' -PRIVACY_LINK = 'https://legal.hackersatupc.org/hackupc/privacy_and_cookies' -TERMS_LINK = 'https://legal.hackersatupc.org/hackupc/terms_and_conditions' +CODE_CONDUCT_LINK = "https://legal.hackersatupc.org/hackupc/code_of_conduct" +LEGAL_LINK = "https://legal.hackersatupc.org/hackupc/legal_notice" +PRIVACY_LINK = "https://legal.hackersatupc.org/hackupc/privacy_and_cookies" +TERMS_LINK = "https://legal.hackersatupc.org/hackupc/terms_and_conditions" From 5a110eaa1e120b3e0a06ad0d1ae01caed55469c5 Mon Sep 17 00:00:00 2001 From: enric4000 Date: Mon, 9 Feb 2026 19:06:56 +0100 Subject: [PATCH 02/13] Made Volunteer pages readOnly --- app/templates/base.html | 6 +- .../templates/other_application_detail.html | 71 ++++++++++--------- organizers/views.py | 40 ++++++----- 3 files changed, 60 insertions(+), 57 deletions(-) diff --git a/app/templates/base.html b/app/templates/base.html index 1a6d4b2be..de0228562 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -111,10 +111,8 @@ {% endif %} {% if request.user.is_organizer %} - {% if request.user.has_volunteer_access %} -
  • Volunteer
  • - {% endif %} +
  • Volunteer
  • {% if request.user.has_mentor_access %}
  • Mentor
  • diff --git a/organizers/templates/other_application_detail.html b/organizers/templates/other_application_detail.html index 8a2bf232b..0978fe018 100644 --- a/organizers/templates/other_application_detail.html +++ b/organizers/templates/other_application_detail.html @@ -20,7 +20,7 @@

    Personal

    {% include 'include/field.html' with desc='Name' value=app.name %} {% elif app.user.is_volunteer %} {% include 'include/field.html' with desc='Name' value=app.user.name %} - {% include 'include/field.html' with desc='Is over 18?' value=app.under_age|yesno %} + {% include 'include/field.html' with desc='Is underage?' value=app.under_age|yesno %} {% include 'include/field.html' with desc='Studies and course/graduation' value=app.studies_and_course %} {% else %} {% include 'include/field.html' with desc='Name' value=app.user.name %} @@ -35,7 +35,7 @@

    Personal

    {% include 'include/field.html' with desc='Gender' value=app.get_gender_display %} {% include 'include/field.html' with desc='Other gender' value=app.other_gender %} {% include 'include/field.html' with desc='In BCN Apr-May' value=app.lennyface|yesno %} -
    +

    Validation

    @@ -45,16 +45,18 @@

    Validation

    {% csrf_token %} - + {% if user.has_volunteer_access %} + + {% endif %}

    -
    +

    Volunteering

    @@ -188,7 +190,7 @@

    {{ comment.text }}

    {% csrf_token %} + rows="3" required maxlength="500"> @@ -198,28 +200,29 @@

    {{ comment.text }}

    {% block out_panel %} {% if app and not app.user.is_sponsor %} - - + {% if app and app.user.is_volunteer and not user.has_volunteer_access %} + {% else %} + + {% endif %} {% endif %} {% endblock %} diff --git a/organizers/views.py b/organizers/views.py index 01c6a18d0..723c2377b 100644 --- a/organizers/views.py +++ b/organizers/views.py @@ -85,7 +85,9 @@ def add_vote(application, user, tech_rat, pers_rat): v.save() votes_count = application.vote_set.count() if votes_count >= 5 and not application.cv_flagged: - AcceptedResume.objects.update_or_create(application=application, defaults={'accepted': True}) + AcceptedResume.objects.update_or_create( + application=application, defaults={"accepted": True} + ) return v @@ -359,12 +361,12 @@ def post(self, request, *args, **kwargs): id_ = request.POST.get("app_id") application = models.HackerApplication.objects.get(pk=id_) - comment_text = request.POST.get('comment_text', None) - motive_of_ban = request.POST.get('motive_of_ban', None) - dubious_type = request.POST.get('dubious_type', None) - dubious_comment_text = request.POST.get('dubious_comment_text', None) + comment_text = request.POST.get("comment_text", None) + motive_of_ban = request.POST.get("motive_of_ban", None) + dubious_type = request.POST.get("dubious_type", None) + dubious_comment_text = request.POST.get("dubious_comment_text", None) - if request.POST.get('add_comment'): + if request.POST.get("add_comment"): add_comment(application, request.user, comment_text) elif request.POST.get("invite") and request.user.is_director: self.invite_application(application) @@ -376,7 +378,7 @@ def post(self, request, *args, **kwargs): self.waitlist_application(application) elif request.POST.get("slack") and request.user.is_organizer: self.slack_invite(application) - elif request.POST.get('set_dubious') and request.user.is_organizer: + elif request.POST.get("set_dubious") and request.user.is_organizer: application.set_dubious(request.user, dubious_type, dubious_comment_text) elif request.POST.get("set_flagged_cv") and request.user.is_organizer: application.set_flagged_cv() @@ -510,8 +512,8 @@ def post(self, request, *args, **kwargs): tech_vote = request.POST.get("tech_rat", None) pers_vote = request.POST.get("pers_rat", None) comment_text = request.POST.get("comment_text", None) - dubious_type = request.POST.get('dubious_type', None) - dubious_comment_text = request.POST.get('dubious_comment_text', None) + dubious_type = request.POST.get("dubious_type", None) + dubious_comment_text = request.POST.get("dubious_comment_text", None) application = models.HackerApplication.objects.get( pk=request.POST.get("app_id") @@ -525,7 +527,9 @@ def post(self, request, *args, **kwargs): "/applications/hacker/review/" + application.uuid_str ) elif request.POST.get("set_dubious"): - application.set_dubious(request.user, dubious_type, dubious_comment_text) + application.set_dubious( + request.user, dubious_type, dubious_comment_text + ) elif request.POST.get("unset_dubious"): application.unset_dubious() elif request.POST.get("set_flagged_cv") and request.user.is_organizer: @@ -615,8 +619,8 @@ def post(self, request, *args, **kwargs): tech_vote = request.POST.get("tech_rat", None) pers_vote = request.POST.get("pers_rat", None) comment_text = request.POST.get("comment_text", None) - dubious_type = request.POST.get('dubious_type', None) - dubious_comment_text = request.POST.get('dubious_comment_text', None) + dubious_type = request.POST.get("dubious_type", None) + dubious_comment_text = request.POST.get("dubious_comment_text", None) application = models.HackerApplication.objects.get( pk=request.POST.get("app_id") @@ -630,7 +634,9 @@ def post(self, request, *args, **kwargs): "/applications/hacker/review/" + application.uuid_str ) elif request.POST.get("set_dubious"): - application.set_dubious(request.user, dubious_type, dubious_comment_text) + application.set_dubious( + request.user, dubious_type, dubious_comment_text + ) elif request.POST.get("unset_dubious"): application.unset_dubious() elif request.POST.get("set_flagged_cv") and request.user.is_organizer: @@ -846,9 +852,7 @@ def get_context_data(self, **kwargs): return context -class VolunteerApplicationsListView( - HaveVolunteerPermissionMixin, _OtherApplicationsListView -): +class VolunteerApplicationsListView(IsOrganizerMixin, _OtherApplicationsListView): table_class = VolunteerListTable filterset_class = VolunteerFilter @@ -912,9 +916,7 @@ def get_current_tabs(self): return mentor_tabs(self.request.user) -class ReviewVolunteerApplicationView( - TabsViewMixin, HaveVolunteerPermissionMixin, TemplateView -): +class ReviewVolunteerApplicationView(IsOrganizerMixin, TabsViewMixin, TemplateView): template_name = "other_application_detail.html" def get_application(self, kwargs): From dfc20d1ec97c6b8c3cbcd7777997ede0cd4680a8 Mon Sep 17 00:00:00 2001 From: enric4000 Date: Mon, 9 Feb 2026 19:09:15 +0100 Subject: [PATCH 03/13] Removed unused variable --- organizers/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/organizers/views.py b/organizers/views.py index 723c2377b..84e4ff239 100644 --- a/organizers/views.py +++ b/organizers/views.py @@ -61,7 +61,6 @@ IsOrganizerMixin, IsDirectorMixin, HaveDubiousPermissionMixin, - HaveVolunteerPermissionMixin, HaveSponsorPermissionMixin, HaveMentorPermissionMixin, IsBlacklistAdminMixin, From 595974760d66752b743ec828a2260a9a64519477 Mon Sep 17 00:00:00 2001 From: polmf <99polmf@gmail.com> Date: Thu, 19 Feb 2026 12:51:02 +0100 Subject: [PATCH 04/13] mails --- .../mails/devpost_upload_message.html | 21 +++++++++++++ .../mails/devpost_upload_subject.txt | 1 + .../mails/reject_receipt_message.html | 2 +- .../mails/ticket_accepted_message.html | 31 +++++++++++++++++++ .../mails/ticket_accepted_subject.txt | 1 + .../mails/travel_tickets_upload_message.html | 22 +++++++++++++ .../mails/travel_tickets_upload_subject.txt | 1 + 7 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 reimbursement/templates/mails/devpost_upload_message.html create mode 100644 reimbursement/templates/mails/devpost_upload_subject.txt create mode 100644 reimbursement/templates/mails/ticket_accepted_message.html create mode 100644 reimbursement/templates/mails/ticket_accepted_subject.txt create mode 100644 reimbursement/templates/mails/travel_tickets_upload_message.html create mode 100644 reimbursement/templates/mails/travel_tickets_upload_subject.txt diff --git a/reimbursement/templates/mails/devpost_upload_message.html b/reimbursement/templates/mails/devpost_upload_message.html new file mode 100644 index 000000000..da62d5e7e --- /dev/null +++ b/reimbursement/templates/mails/devpost_upload_message.html @@ -0,0 +1,21 @@ +{% extends 'base_email.html' %} +{% block preheader %}Upload your Devpost link{% endblock %} + +{% block content %} +

    Hey {{ app.user.get_full_name }},

    +
    +

    HackUPC has officially come to an end, and we hope you enjoyed the event as much as we did!

    +

    The next step to get your travel reimbursement is to upload the link to your project on Devpost. This will allow us + to validate your submission and verify that the project is valid and that it complies with the requirements that + were previously communicated.

    + +

    Upload your link here: https://my.hackupc.com/reimbursement/dash_board/. +

    + +{% include 'mails/include/email_button.html' with text='Upload Devpost link' url=form_url %} + +

    Thank you for your collaboration!!

    + +{% include 'mails/include/closing.html' with travel="true" %} +{% endblock %} \ No newline at end of file diff --git a/reimbursement/templates/mails/devpost_upload_subject.txt b/reimbursement/templates/mails/devpost_upload_subject.txt new file mode 100644 index 000000000..f99021acc --- /dev/null +++ b/reimbursement/templates/mails/devpost_upload_subject.txt @@ -0,0 +1 @@ +[ACTION REQUIRED] Upload your Devpost link diff --git a/reimbursement/templates/mails/reject_receipt_message.html b/reimbursement/templates/mails/reject_receipt_message.html index b5461b9a0..066c62e08 100644 --- a/reimbursement/templates/mails/reject_receipt_message.html +++ b/reimbursement/templates/mails/reject_receipt_message.html @@ -5,7 +5,7 @@

    Hey {{ app.user.get_full_name }},


    The receipt you submitted is invalid.

    -

    Comment on receipt submitted: {{ reimb.public_comment }}

    +

    Rejected reason: {{ reimb.public_comment }}

    {% include 'mails/include/email_button.html' with text='Submit receipt' url=form_url %}

    diff --git a/reimbursement/templates/mails/ticket_accepted_message.html b/reimbursement/templates/mails/ticket_accepted_message.html new file mode 100644 index 000000000..12d2f87d7 --- /dev/null +++ b/reimbursement/templates/mails/ticket_accepted_message.html @@ -0,0 +1,31 @@ +{% extends 'base_email.html' %} +{% block preheader %}Your ticket has been accepted{% endblock %} + +{% block content %} +

    Hey {{ app.user.get_full_name }},

    +
    +

    If you are receiving this mail is because your travel ticket has been accepted.

    +

    However, the process requires you to follow the below instructions during the event to consider you eligible for a + reimbursement.

    + +

    Regarding the project:

    +
      +
    • The participant must demo a valid project to the HackUPC judges.
    • +
    • The Devpost username must be similar to the participant’s real name. Usernames that do not reasonably resemble + the real name (e.g. TheBest) will not be accepted.
    • +
    • The completeness of the GitHub repository will be taken into account, including aspects such as regular commits + throughout the weekend.
    • +
    + +

    Regarding the attendance:

    +
      +
    • It must be demonstrable that the participant attended the event and stayed at the venue for a minimum period of + participation. Attendance to meals, activities, workshops, and other official event activities may be taken into + account to verify participation.
    • +
    + +

    The money will be transferred to the participant using PayPal in the following 60 days after the event. The paypal + account MUST exist.

    + +{% include 'mails/include/closing.html' with travel="true" %} +{% endblock %} \ No newline at end of file diff --git a/reimbursement/templates/mails/ticket_accepted_subject.txt b/reimbursement/templates/mails/ticket_accepted_subject.txt new file mode 100644 index 000000000..4498c9a44 --- /dev/null +++ b/reimbursement/templates/mails/ticket_accepted_subject.txt @@ -0,0 +1 @@ +[IMPORTANT INFORMATION ] Your ticket has been accepted diff --git a/reimbursement/templates/mails/travel_tickets_upload_message.html b/reimbursement/templates/mails/travel_tickets_upload_message.html new file mode 100644 index 000000000..3b84a1383 --- /dev/null +++ b/reimbursement/templates/mails/travel_tickets_upload_message.html @@ -0,0 +1,22 @@ +{% extends 'base_email.html' %} +{% block preheader %}Upload your travel tickets{% endblock %} + +{% block content %} +

    Hey {{ app.user.get_full_name }},

    +
    +

    For us to accept your travel reimbursement request, you need to upload VALID travel tickets in https://my.hackupc.com/reimbursement/dash_board/. +

    +

    A ticket is considered valid if and only if:

    +
      +
    • The receipts are in the name of the participant requesting the reimbursement.
    • +
    • It includes a detailed price breakdown, the purchase date, and the travel dates, clearly indicating which costs + correspond to the travel itself and which relate to additional services (e.g. extra luggage).
    • +
    • The travel dates are within one week before or one week after the event.
    • +
    • The ticket is uploaded before the start of the event.
    • +
    + +{% include 'mails/include/email_button.html' with text='Upload tickets' url=form_url %} + +{% include 'mails/include/closing.html' with travel="true" %} +{% endblock %} \ No newline at end of file diff --git a/reimbursement/templates/mails/travel_tickets_upload_subject.txt b/reimbursement/templates/mails/travel_tickets_upload_subject.txt new file mode 100644 index 000000000..b96ef8343 --- /dev/null +++ b/reimbursement/templates/mails/travel_tickets_upload_subject.txt @@ -0,0 +1 @@ +[ACTION REQUIRED] Upload your travel tickets From a2e8ebb0e85ea2835e290028fabf7ffa4584a78b Mon Sep 17 00:00:00 2001 From: polmf <99polmf@gmail.com> Date: Thu, 19 Feb 2026 18:43:16 +0100 Subject: [PATCH 05/13] mails autoamtics --- app/hackathon_variables.py | 9 +++- applications/views.py | 6 +++ cronjob | 2 + reimbursement/emails.py | 49 ++++++++++++++++--- .../commands/send_devpost_emails.py | 38 ++++++++++++++ .../migrations/0015_auto_20260219_1731.py | 30 ++++++++++++ reimbursement/models.py | 8 ++- reimbursement/views.py | 4 +- 8 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 reimbursement/management/commands/send_devpost_emails.py create mode 100644 reimbursement/migrations/0015_auto_20260219_1731.py diff --git a/app/hackathon_variables.py b/app/hackathon_variables.py index 66f089726..92a280615 100644 --- a/app/hackathon_variables.py +++ b/app/hackathon_variables.py @@ -60,6 +60,11 @@ # (OPTIONAL) When to arrive at the hackathon HACKATHON_LEAVE = "" +# (OPTIONAL) When the event ends (to send Devpost link emails) +HACKATHON_EVENT_END = timezone.datetime( + 2026, 4, 26, 20, 00, tzinfo=timezone.pytz.timezone(TIME_ZONE) +) + # (OPTIONAL) Hackathon live page HACKATHON_LIVE_PAGE = "https://live.hackupc.com" @@ -80,11 +85,11 @@ DEFAULT_REIMBURSEMENT_AMOUNT = 100 CURRENCY = "€" REIMBURSEMENT_EXPIRY_DATE = timezone.datetime( - 2025, 5, 2, 17, 00, tzinfo=timezone.pytz.timezone(TIME_ZONE) + 2026, 5, 2, 17, 00, tzinfo=timezone.pytz.timezone(TIME_ZONE) ) REIMBURSEMENT_REQUIREMENTS = "You have to submit a project and demo it during the event in order to get reimbursed" REIMBURSEMENT_DEADLINE = timezone.datetime( - 2025, 5, 5, 23, 59, tzinfo=timezone.pytz.timezone(TIME_ZONE) + 2026, 5, 5, 23, 59, tzinfo=timezone.pytz.timezone(TIME_ZONE) ) # (OPTIONAL) Max team members. Defaults to 4 diff --git a/applications/views.py b/applications/views.py index b61dd5936..63877e8af 100644 --- a/applications/views.py +++ b/applications/views.py @@ -80,6 +80,12 @@ def get(self, request, *args, **kwargs): if msg: msg.send() + if application.user.is_hacker() and application.reimb: + from reimbursement import emails as reimb_emails + reimb_msg = reimb_emails.create_travel_tickets_upload_email( + application.user.reimbursement, request + ) + reimb_msg.send() try: slack.send_slack_invite(request.user.email) # Ignore if we can't send, it's only optional diff --git a/cronjob b/cronjob index eb4a12213..9e7e140db 100644 --- a/cronjob +++ b/cronjob @@ -2,3 +2,5 @@ 6 */2 * * * root /usr/local/bin/python /code/manage.py expire_applications # Run expire reimbursements job at 6 minutes past the hour, every 2 hours 36 */2 * * * root /usr/local/bin/python /code/manage.py expire_reimbursements +# Run send devpost emails job every Sunday at 20:00 +0 20 * * 0 root /usr/local/bin/python /code/manage.py send_devpost_emails diff --git a/reimbursement/emails.py b/reimbursement/emails.py index 8a2dd1055..59085cc7a 100644 --- a/reimbursement/emails.py +++ b/reimbursement/emails.py @@ -25,15 +25,50 @@ def create_no_reimbursement_email(reimb, request): return emails.render_mail("mails/no_reimbursement", reimb.hacker.email, c) +def create_travel_tickets_upload_email(reimb, request): + app = reimb.hacker.application + c = _get_context(app, reimb, request) + return emails.render_mail( + "mails/travel_tickets_upload", reimb.hacker.email, c, action_required=True + ) + + +def create_ticket_accepted_email(reimb, request): + app = reimb.hacker.application + c = _get_context(app, reimb, request) + return emails.render_mail("mails/ticket_accepted", reimb.hacker.email, c) + + +def create_devpost_upload_email(reimb, request): + app = reimb.hacker.application + c = _get_context(app, reimb, request) + return emails.render_mail( + "mails/devpost_upload", reimb.hacker.email, c, action_required=True + ) + + +def create_project_invalidated_email(reimb, request): + app = reimb.hacker.application + c = _get_context(app, reimb, request) + return emails.render_mail("mails/project_invalidated", reimb.hacker.email, c) + + def _get_context(app, reimb, request): + from django.conf import settings + + confirm_url = reverse("confirm_app", kwargs={"id": app.uuid_str}, request=request) + form_url = reverse("reimbursement_dashboard", request=request) + cancel_url = reverse("cancel_app", kwargs={"id": app.uuid_str}, request=request) + if not request: + base_url = "https://" + settings.HACKATHON_DOMAIN + confirm_url = base_url + confirm_url + form_url = base_url + form_url + cancel_url = base_url + cancel_url + return { "app": app, "reimb": reimb, - "confirm_url": str( - reverse("confirm_app", kwargs={"id": app.uuid_str}, request=request) - ), - "form_url": str(reverse("reimbursement_dashboard", request=request)), - "cancel_url": str( - reverse("cancel_app", kwargs={"id": app.uuid_str}, request=request) - ), + "confirm_url": str(confirm_url), + "form_url": str(form_url), + "cancel_url": str(cancel_url), } diff --git a/reimbursement/management/commands/send_devpost_emails.py b/reimbursement/management/commands/send_devpost_emails.py new file mode 100644 index 000000000..dd66def47 --- /dev/null +++ b/reimbursement/management/commands/send_devpost_emails.py @@ -0,0 +1,38 @@ +from django.core.management.base import BaseCommand +from django.utils import timezone +from django.conf import settings +from reimbursement.models import Reimbursement, RE_APPROVED +from reimbursement import emails + +class Command(BaseCommand): + help = 'Sends automatic emails to upload Devpost link to approved reimbursements' + + def handle(self, *args, **options): + # Check if the event has ended + if timezone.now() < settings.HACKATHON_EVENT_END: + self.stdout.write(self.style.WARNING('Event has not ended yet. Skipping Devpost emails.')) + return + + # We target reimbursements that have the receipt approved (RE_APPROVED), + # hasn't uploaded a devpost link yet (devpost=''), + # and we haven't sent the reminder email yet (devpost_email_sent=False). + reimbursements = Reimbursement.objects.filter( + status=RE_APPROVED, + devpost='', + devpost_email_sent=False + ) + + self.stdout.write(f'Found {reimbursements.count()} reimbursements pending Devpost link.') + + sent_count = 0 + for reimb in reimbursements: + try: + msg = emails.create_devpost_upload_email(reimb, None) + msg.send() + reimb.devpost_email_sent = True + reimb.save() + sent_count += 1 + except Exception as e: + self.stderr.write(f'Error sending email to {reimb.hacker.email}: {str(e)}') + + self.stdout.write(self.style.SUCCESS(f'Successfully sent {sent_count} emails.')) diff --git a/reimbursement/migrations/0015_auto_20260219_1731.py b/reimbursement/migrations/0015_auto_20260219_1731.py new file mode 100644 index 000000000..b8ddb4405 --- /dev/null +++ b/reimbursement/migrations/0015_auto_20260219_1731.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.23 on 2026-02-19 17:31 + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('reimbursement', '0014_alter_reimbursement_expiration_time'), + ] + + operations = [ + migrations.AddField( + model_name='reimbursement', + name='devpost_email_sent', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='reimbursement', + name='expiration_time', + field=models.DateTimeField(default=datetime.datetime(2026, 5, 2, 16, 0, tzinfo=utc)), + ), + migrations.AlterField( + model_name='reimbursement', + name='status', + field=models.CharField(choices=[('W', 'Wait listed'), ('PT', 'Pending receipt submission'), ('PA', 'Pending receipt approval'), ('A', 'Receipt approved'), ('X', 'Expired'), ('FS', 'Friend submission'), ('F', 'Reimbursement Approved'), ('PD', 'Pending demo validation'), ('I', 'Invalid')], default='PT', max_length=2), + ), + ] diff --git a/reimbursement/models.py b/reimbursement/models.py index c89fa6575..0fd238db8 100644 --- a/reimbursement/models.py +++ b/reimbursement/models.py @@ -19,6 +19,7 @@ RE_FINALIZED = "F" RE_EXPIRED = "X" RE_FRIEND_SUBMISSION = "FS" +RE_INVALID = "I" RE_STATUS = [ (RE_WAITLISTED, "Wait listed"), @@ -29,6 +30,7 @@ (RE_FRIEND_SUBMISSION, "Friend submission"), (RE_FINALIZED, "Reimbursement Approved"), (RE_PEND_DEMO_VAL, "Pending demo validation"), + (RE_INVALID, "Invalid"), ] @@ -65,6 +67,7 @@ class Reimbursement(models.Model): public_comment = models.CharField(max_length=300, null=True, blank=True) devpost = models.URLField(blank=True, null=True, default="") + devpost_email_sent = models.BooleanField(default=False) # User controlled paypal_email = models.EmailField(null=True, blank=True) @@ -157,9 +160,10 @@ def validate(self, user): self.reimbursed_by = user self.save() - def invalidate(self, user): + def invalidate(self, user, reason): if self.status == RE_PEND_DEMO_VAL: - self.status = RE_WAITLISTED + self.status = RE_INVALID + self.public_comment = reason self.status_update_date = timezone.now() self.reimbursed_by = user self.save() diff --git a/reimbursement/views.py b/reimbursement/views.py index 993e93357..890c32f32 100644 --- a/reimbursement/views.py +++ b/reimbursement/views.py @@ -170,7 +170,8 @@ def post(self, request, *args, **kwargs): elif "invalidate" in request.POST: id_ = kwargs.get("id", None) reimb = models.Reimbursement.objects.get(pk=id_) - reimb.invalidate(request.user) + reimb.invalidate(request.user, request.POST.get("invalidate_reason")) + emails.create_project_invalidated_email(reimb, request).send() messages.success(request, "Reimbursement invalidated") else: id_ = request.POST.get("id", None) @@ -183,6 +184,7 @@ def post(self, request, *args, **kwargs): if a_form.is_valid(): a_form.save(commit=False) a_form.instance.accept_receipt(request.user) + emails.create_ticket_accepted_email(a_form.instance, request).send() a_form.save() messages.success(request, "Receipt accepted") else: From 81dba97b04504490ec2f3ed542bc4aa3c2af838f Mon Sep 17 00:00:00 2001 From: polmf <99polmf@gmail.com> Date: Thu, 19 Feb 2026 19:56:47 +0100 Subject: [PATCH 06/13] fix --- reimbursement/management/commands/send_devpost_emails.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/reimbursement/management/commands/send_devpost_emails.py b/reimbursement/management/commands/send_devpost_emails.py index dd66def47..12c7b101c 100644 --- a/reimbursement/management/commands/send_devpost_emails.py +++ b/reimbursement/management/commands/send_devpost_emails.py @@ -4,6 +4,7 @@ from reimbursement.models import Reimbursement, RE_APPROVED from reimbursement import emails + class Command(BaseCommand): help = 'Sends automatic emails to upload Devpost link to approved reimbursements' @@ -21,9 +22,9 @@ def handle(self, *args, **options): devpost='', devpost_email_sent=False ) - + self.stdout.write(f'Found {reimbursements.count()} reimbursements pending Devpost link.') - + sent_count = 0 for reimb in reimbursements: try: @@ -34,5 +35,5 @@ def handle(self, *args, **options): sent_count += 1 except Exception as e: self.stderr.write(f'Error sending email to {reimb.hacker.email}: {str(e)}') - + self.stdout.write(self.style.SUCCESS(f'Successfully sent {sent_count} emails.')) From f437349a5a14f1404002c6107d2e03d3f3b4dede Mon Sep 17 00:00:00 2001 From: polmf <99polmf@gmail.com> Date: Thu, 19 Feb 2026 19:59:17 +0100 Subject: [PATCH 07/13] fix --- reimbursement/management/commands/send_devpost_emails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reimbursement/management/commands/send_devpost_emails.py b/reimbursement/management/commands/send_devpost_emails.py index 12c7b101c..2f24f6bd6 100644 --- a/reimbursement/management/commands/send_devpost_emails.py +++ b/reimbursement/management/commands/send_devpost_emails.py @@ -24,7 +24,7 @@ def handle(self, *args, **options): ) self.stdout.write(f'Found {reimbursements.count()} reimbursements pending Devpost link.') - + sent_count = 0 for reimb in reimbursements: try: From 2e608118b10a4788a41ad36e0dd630ccdb82efcc Mon Sep 17 00:00:00 2001 From: polmf <99polmf@gmail.com> Date: Thu, 19 Feb 2026 23:36:49 +0100 Subject: [PATCH 08/13] imatges wallet --- app/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/utils.py b/app/utils.py index bbee6463e..b0ce14b98 100644 --- a/app/utils.py +++ b/app/utils.py @@ -235,7 +235,7 @@ def generateGTicketUrl(qrValue: str): }, "logo": { "sourceUri": { - "uri": "https://i.ibb.co/b5TQV4md/hackupc2025.png", + "uri": "https://i.ibb.co/g2GGdMB/Copy-of-Isotipo-B-6.png", }, "contentDescription": { "defaultValue": { @@ -253,7 +253,7 @@ def generateGTicketUrl(qrValue: str): "imageModulesData": [ { "mainImage": { - "sourceUri": {"uri": "https://hackupc.com/ogimage.png"}, + "sourceUri": {"uri": "https://hackupc.com/og_image2026.png"}, "contentDescription": { "defaultValue": {"language": "en-US", "value": "Event picture"} }, From d56573e19a68c49a7125a41c5a2ce2b820f31494 Mon Sep 17 00:00:00 2001 From: EncryptEx <41539618+EncryptEx@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:00:25 +0100 Subject: [PATCH 09/13] style: updated wallet card for 2026 edition --- app/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/utils.py b/app/utils.py index b0ce14b98..b5dd4fdda 100644 --- a/app/utils.py +++ b/app/utils.py @@ -300,10 +300,10 @@ def generateGTicketUrl(qrValue: str): }, "dateTime": {"start": "2026-04-24T16:00", "end": "2026-04-26T17:00"}, "reviewStatus": "UNDER_REVIEW", - "hexBackgroundColor": "#231F20", + "hexBackgroundColor": "#5B5340", "heroImage": { "sourceUri": { - "uri": "https://i.ibb.co/LL5T8d0/walletv2.png", + "uri": "https://i.ibb.co/6Rkp3P6M/og-image2026.png", }, "contentDescription": { "defaultValue": { From 37e90dba7e3e45bf3364f78f49f660daf3c179c8 Mon Sep 17 00:00:00 2001 From: polmf <99polmf@gmail.com> Date: Fri, 20 Feb 2026 13:02:21 +0100 Subject: [PATCH 10/13] wallet final --- app/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/utils.py b/app/utils.py index b0ce14b98..a356f9fdf 100644 --- a/app/utils.py +++ b/app/utils.py @@ -300,10 +300,10 @@ def generateGTicketUrl(qrValue: str): }, "dateTime": {"start": "2026-04-24T16:00", "end": "2026-04-26T17:00"}, "reviewStatus": "UNDER_REVIEW", - "hexBackgroundColor": "#231F20", + "hexBackgroundColor": "#AADEFE", "heroImage": { "sourceUri": { - "uri": "https://i.ibb.co/LL5T8d0/walletv2.png", + "uri": "https://i.ibb.co/6Rkp3P6M/og-image2026.png", }, "contentDescription": { "defaultValue": { From e02e424144db0cd885d2a45676314d032184948e Mon Sep 17 00:00:00 2001 From: EncryptEx <41539618+EncryptEx@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:17:16 +0100 Subject: [PATCH 11/13] style: fix alt on banner --- app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/utils.py b/app/utils.py index a356f9fdf..a686d9ee6 100644 --- a/app/utils.py +++ b/app/utils.py @@ -308,7 +308,7 @@ def generateGTicketUrl(qrValue: str): "contentDescription": { "defaultValue": { "language": "en-US", - "value": "HackUPC galactic banner", + "value": "HackUPC 2026 Banner", }, }, }, From 7758f3e681001d2b958f09a0c23fad4bc05e915f Mon Sep 17 00:00:00 2001 From: EncryptEx <41539618+EncryptEx@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:44:53 +0100 Subject: [PATCH 12/13] final version --- app/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/utils.py b/app/utils.py index a686d9ee6..d784b52cb 100644 --- a/app/utils.py +++ b/app/utils.py @@ -303,7 +303,7 @@ def generateGTicketUrl(qrValue: str): "hexBackgroundColor": "#AADEFE", "heroImage": { "sourceUri": { - "uri": "https://i.ibb.co/6Rkp3P6M/og-image2026.png", + "uri": "https://i.ibb.co/nN4GxfFt/imatge-wallet.png", }, "contentDescription": { "defaultValue": { From db59b85216a9808709cbd9f2038cae3fc5e9cea3 Mon Sep 17 00:00:00 2001 From: Yaoyao Date: Sun, 22 Feb 2026 13:04:43 +0100 Subject: [PATCH 13/13] fix punts --- applications/templates/include/application_form.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/templates/include/application_form.html b/applications/templates/include/application_form.html index 950b970c1..3f9d70b99 100644 --- a/applications/templates/include/application_form.html +++ b/applications/templates/include/application_form.html @@ -19,7 +19,7 @@

    ❗ Before filling out the application, read this article about - How To Do a Good Application.. + How To Do a Good Application.

    {% endif %}