diff --git a/app/hackathon_variables.py b/app/hackathon_variables.py index 493b10f8b..92a280615 100644 --- a/app/hackathon_variables.py +++ b/app/hackathon_variables.py @@ -4,61 +4,77 @@ 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) 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' +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 +83,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( + 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( + 2026, 5, 5, 23, 59, tzinfo=timezone.pytz.timezone(TIME_ZONE) +) # (OPTIONAL) Max team members. Defaults to 4 TEAMS_ENABLED = True @@ -82,9 +102,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 +115,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 +139,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 +149,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" 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/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 %} 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/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..84e4ff239 100644 --- a/organizers/views.py +++ b/organizers/views.py @@ -61,7 +61,6 @@ IsOrganizerMixin, IsDirectorMixin, HaveDubiousPermissionMixin, - HaveVolunteerPermissionMixin, HaveSponsorPermissionMixin, HaveMentorPermissionMixin, IsBlacklistAdminMixin, @@ -85,7 +84,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 +360,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 +377,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 +511,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 +526,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 +618,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 +633,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 +851,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 +915,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): 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..2f24f6bd6 --- /dev/null +++ b/reimbursement/management/commands/send_devpost_emails.py @@ -0,0 +1,39 @@ +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/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:

    + + +

    Regarding the attendance:

    + + +

    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:

    + + +{% 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 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: