Skip to content

Commit 0b01a9d

Browse files
authored
Merge pull request #41 from HackAssistant/production
Production
2 parents fdaede4 + 830c0ea commit 0b01a9d

File tree

12 files changed

+276
-83
lines changed

12 files changed

+276
-83
lines changed

DEV.md

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,51 @@
11
# Documentation
2-
## Forms
2+
## BootstrapFormMixin
3+
BootstrapFormMixin is a class that allows an easy way to render any form type.
4+
5+
At the start of the class you will need to override the `bootstrap_field_info` where all the render information is stored.
6+
```python
7+
bootstrap_field_info = {'TITLE': {
8+
'fields': [{'name': 'FIELD_NAME', 'space': GRID_NUMBER, 'visible': VALUE_DICT},],
9+
'description': 'DESCRIPTION'},}
10+
```
11+
In this example the TITLE will be the h2 text of the formset that will contain all the fields on the array.
12+
The description is an optional text to be displayed after the TITLE.
13+
The FIELD_NAME will be the names of the fields of your form, this is required.
14+
GRID_NUMBER will be the grid (1-12) space that will fill your field in the Bootstrap row, if omitted the space will be 0.
15+
Lastly, the VALUE_DICT refers to a dictionary that will tell your field when can be visible, if omitted the field will always be visible.
16+
IMPORTANT: if you miss a field on the `bootstrap_field_info` it will set to not required.
17+
18+
An important feature of this form, is the use of the TypeHead lib for autocomplete CharFields, so the user has already some possible answers.
19+
This can be done by adding the `api_fields` .
20+
This information is stored in the `api_fields` to the Meta class inside your form, where the API url is required. Other optional fields are `restrict` and
21+
`others` field to restrict the options of the field or add the Others option. For example, in this application,
22+
it has been used to list all the possible countries and avoid custom answers.
23+
24+
```python
25+
class Meta(ApplicationForm.Meta):
26+
api_fields = {
27+
'country': {'url': static('data/countries.json'), 'restrict': True, 'others': True},
28+
'university': {'url': static('data/universities.json')},
29+
'degree': {'url': static('data/degrees.json')},
30+
}
31+
```
32+
33+
### Methods
34+
35+
#### get_bootstrap_field_info
36+
37+
If you want to dynamically change the `bootstrap_field_info` you can override this method.
38+
It's important to not remove the initial call `super` to get a COPY of the `bootstrap_field_info` otherwise you will be overriding the full variable.
39+
You will also need to be cautious with `ModelForm` and understanding how forms work.
40+
41+
#### set_read_only
42+
43+
This method is in order to make the form not editable. Keep in mind that you will need to disable the POST method on your view for this condition.
44+
45+
## Application Forms
346
Despite having different types of application, all of them are based in a common structure, defined in the
447
`ApplicationForm` class. Then, for each type (hacker, volunteer, mentor and sponsor) specific parameters are
5-
added.
48+
added. This class inherits the `BootstrapFormMixin`, so all the features of it are available.
649

750
Each class starts with `bootstrap_field_info`, where all the fields of the form that have to be rendered are described.
851
First, it indicates the part of the form which will be modified (e.g. Personal Info, Hackathons,...). Then, each field
@@ -18,7 +61,7 @@ be shown(e.g. when diet is "Other", the applicant should specify their case.)
1861

1962
If the information that contains the field was previously specified (e.g. diet, gender), it won't require additional
2063
methods. However, if a new one is introduced (e.g. `under_age` in `HackerForm`), the corresponding class will include a
21-
method defining the type of answer, if it's required, it's possible choices (which sometimes may be defined as constants
64+
method defining the type of answer, if it's required, its possible choices (which sometimes may be defined as constants
2265
at the beginning of the file) and the default value amongst others.
2366

2467
```python
@@ -35,23 +78,29 @@ The method `exclude_save` is used to not store the information of the specified
3578
have to be accepted in order to participate in the event. Therefore, once the application is submitted, this field will
3679
always be `True`, so it's not necessary to consider that information anymore.
3780

38-
The `Meta` class is useful to link a json file to a specific field, so the user has already some possible answers.
39-
This information is stored in the `api_fields` where the API url is required. Other optional fields are `restrict` and
40-
`others` field to restrict the options of the field or add the Others option. For example, in this application,
41-
it has been used to list all the possible countries and avoid custom answers.
42-
43-
```python
44-
class Meta(ApplicationForm.Meta):
45-
api_fields = {
46-
'country': {'url': static('data/countries.json'), 'restrict': True, 'others': True},
47-
'university': {'url': static('data/universities.json')},
48-
'degree': {'url': static('data/degrees.json')},
49-
}
50-
```
51-
5281
It's important to notice that all the text that will be printed out is surrounded by a low bash and parentheses. In
5382
future versions, this will allow to translate the displayed text in an easy way.
5483

5584
```python
5685
label = _('How old are you?')
5786
```
87+
88+
## TabsViewMixin
89+
90+
This is a simple Mixin to display Tab navigation on your TemplateView or any View class that inherits this class.
91+
The navigation tabs are the navigation that is displayed on top of your template container.
92+
93+
### Methods
94+
95+
#### get_current_tabs
96+
This method is meant to be overridden and return an array to be with tuples. The first element of the tuple will be the text of the tab and the second will be the url, those 2 are mandatory.
97+
The 3rd is and optional variable that can be set to None to be omitted and displays a warning symbol.
98+
Finally, the 4th variable is to set the tab to active with a boolean, if None or unset the tab will be active if the path equals the url.
99+
Data can be passed to the method with the `get_context_data` kwargs at the super of your view.
100+
101+
#### get_back_url
102+
Method that returns the url that will be updated to the context with the name `back`.
103+
104+
## PermissionRequiredMixin
105+
This Mixin is mean to inherit any type of View class and expands the base [Django PermissionRequiredMixin](https://docs.djangoproject.com/en/4.1/topics/auth/default/#the-permissionrequiredmixin-mixin) class.
106+
The new functionality is that you can put a dictionary on the `permission_required` in order to change the permissions between the HTTP method: GET, POST, etc.

app/static/css/main.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ footer {
140140
color: black;
141141
}
142142

143+
.bg-dark .bg-contrast {
144+
background-color: rgba(255, 255, 255, 0.075);
145+
}
146+
147+
.bg-light .bg-contrast {
148+
background-color: rgba(0, 0, 0, 0.075);
149+
}
150+
143151
.g-recaptcha {
144152
display: inline-block;
145153
}

app/static/css/theme.scss

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,38 @@ $table-variants: (
3333

3434
@import './bootstrap/bootstrap';
3535

36+
.bg-primary {
37+
color: $dark;
38+
}
39+
40+
.bg-secondary {
41+
color: $white;
42+
}
43+
44+
.bg-info {
45+
color: $black;
46+
}
47+
48+
.bg-danger {
49+
color: $black;
50+
}
51+
52+
.bg-warning {
53+
color: $black;
54+
}
55+
56+
.bg-success {
57+
color: $black;
58+
}
59+
60+
.bg-light {
61+
color: $black;
62+
}
63+
64+
.bg-dark {
65+
color: $white;
66+
}
67+
3668
body.light {
3769
background-image: linear-gradient(darken($primary, 15%), $primary);
3870
}

app/static/lib/QRCode/qrcode.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/templatetags/util.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ def nav_active(text, starts):
2525
return False
2626

2727

28+
@register.filter
29+
def get_type_list(app_list):
30+
return [app.type.name for app in app_list]
31+
32+
2833
@register.simple_tag
2934
def crispy(*args, **kwargs):
3035
return None

application/forms.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ def clean_promotional_code(self):
116116

117117
class Meta:
118118
model = Application
119+
description = ''
119120
exclude = ['user', 'uuid', 'data', 'submission_date', 'status_update_date', 'status', 'contacted_by', 'type',
120121
'last_modified', 'edition']
121122
help_texts = {
@@ -225,6 +226,8 @@ def get_hidden_edit_fields(self):
225226
return hidden_fields
226227

227228
class Meta(ApplicationForm.Meta):
229+
description = _('You will join a team with which you will do a project in a weekend. '
230+
'You will meet new people and learn a lot, don\'t think about it and apply!')
228231
api_fields = {
229232
'country': {'url': static('data/countries.json'), 'restrict': True, 'others': True},
230233
'university': {'url': static('data/universities.json')},
@@ -328,6 +331,9 @@ class VolunteerForm(ApplicationForm):
328331
label=_('How did you discover %s?' % getattr(settings, 'HACKATHON_NAME')))
329332

330333
class Meta(ApplicationForm.Meta):
334+
description = _('Volunteers make the event possible by assisting the hackers and preparing the physical '
335+
'spaces of the event. By joining our team of volunteers, you will get to know how this '
336+
'amazing event works from the inside and meet amazing people and live a great experience!')
331337
api_fields = {
332338
'country': {'url': static('data/countries.json'), 'restrict': True, 'others': True},
333339
'university': {'url': static('data/universities.json')},
@@ -429,6 +435,9 @@ class MentorForm(ApplicationForm):
429435
)
430436

431437
class Meta(ApplicationForm.Meta):
438+
description = _('Help and motivate hackers with your knowledge. Either because you are passionate about it'
439+
', or if you\'ve graduated more than a year ago and can\'t participate as a hacker, '
440+
'apply now as a mentor!')
432441
api_fields = {
433442
'country': {'url': static('data/countries.json'), 'restrict': True, 'others': True},
434443
'university': {'url': static('data/universities.json')},

application/models.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ class ApplicationTypeConfig(models.Model):
6767
auto_confirm = models.BooleanField(default=False, help_text=_('Applications set on status confirmed by default'))
6868
compatible_with_others = models.BooleanField(default=False, help_text=_('User can confirm in more than one type'))
6969

70+
def get_description(self):
71+
from application import forms
72+
aux = forms
73+
lookup = ['%sForm' % self.name.title(), 'Meta', 'description']
74+
for item in lookup:
75+
aux = getattr(aux, item, None)
76+
if aux is None:
77+
return ''
78+
return aux
79+
7080
def get_file_review_fields(self):
7181
try:
7282
return ast.literal_eval(self.file_review_fields)
@@ -211,14 +221,15 @@ class Application(models.Model):
211221
}
212222
STATUS_DESCRIPTION = {
213223
STATUS_NEEDS_CHANGE: _('Your application might have some misleading information. '
214-
'Please edit what the organizing team told you to'),
224+
'Please edit what the organizing team told you to.'),
215225
STATUS_LAST_REMINDER: _('You have been invited but not accepted yet. '
216226
'In less than 24h you will lose your invitation'),
217227
STATUS_PENDING: _('The organizing team is reviewing your application. Please be patient.'),
218228
STATUS_CONFIRMED: _('You have confirmed your application. Can\'t wait seeing you in the hack!'),
219229
STATUS_INVITED: _('Congratulations! You have been invited, accept your invitation.'),
220230
STATUS_CANCELLED: _('You have cancelled your application, we hope to see you next year.'),
221-
STATUS_EXPIRED: _('Your application have been expired. Please contact us quick if you want to come.'),
231+
STATUS_EXPIRED: _('Your application invitation have been expired. '
232+
'Please contact us quick if you want to come.'),
222233
STATUS_INVALID: _('Your application have been invalidated. It seems you cannot join us with this role.'),
223234
STATUS_REJECTED: _('We are so sorry, but our hack is full...'),
224235
STATUS_BLOCKED: _('User was blocked by your organization.'),

application/signals.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django.contrib.auth import get_user_model
12
from django.contrib.auth.models import Group
23
from django.db.models.signals import post_save, post_delete
34
from django.dispatch import receiver
@@ -15,6 +16,8 @@ def clear_groups(sender, instance, **kwargs):
1516
group.user_set.clear()
1617
sender.get_default_edition(force_update=True)
1718
sender.get_last_edition(force_update=True)
19+
user_model = get_user_model()
20+
user_model.objects.update(qr_code='')
1821

1922

2023
@receiver(post_delete, sender=ApplicationTypeConfig)

application/templates/application_home.html

Lines changed: 10 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,20 @@
11
{% extends 'base.html' %}
22
{% load i18n %}
3+
{% load util %}
4+
{% load static %}
35
{% block subtitle %}{% translate 'Home' %}{% endblock %}
6+
{% block head %}
7+
<script type="text/javascript" src="{% static 'lib/QRCode/qrcode.min.js' %}"></script>
8+
{% endblock %}
49
{% block content %}
510
<div>
611
<h1 style="text-align: center">{% translate 'Home' %}</h1>
712
<p>{% blocktrans %}This is the main application of {{ app_hack }}. From here you can apply as all the possible roles and participate to {{ app_hack }}.{% endblocktrans %}</p>
8-
{% if not accepted_application %}
9-
<p>{% blocktrans %}All the applications are reviewed by the organizing team, please be patient if your application is still on review.{% endblocktrans %}</p>
10-
<h2>{% translate 'Applicable roles' %} <a class="h6" target="_blank" href="{{ app_landing }}">{% translate 'More info' %}</a></h2>
11-
12-
{% for application_type in application_types %}
13-
<div class="list-group rounded-3" style="margin-bottom: 10px">
14-
{% if application_type.user_instance and application_type.user_instance.invited %}
15-
<div class="list-group-item list-group-item-{{ application_type.user_instance.get_public_status_color|default:theme }} {% if not application_type.user_instance and not application_type.active %}disabled{% endif %}">
16-
<div class="d-flex w-100 justify-content-between">
17-
<h3 class="mb-1">{{ application_type.name }}</h3>
18-
{% if not application_type.user_instance %}
19-
<small>{% if application_type.closed %}{% translate 'Closed' %}{% elif application_type.active %}{% translate 'Open. Closes in' %} {{ application_type.time_left }}{% else %}{% translate 'Closed. Opens in' %} {{ application_type.time_left }}{% endif %}</small>
20-
{% endif %}
21-
</div>
22-
<p>
23-
<strong>{% translate 'Status' %} {% if not application_type.user_instance %}{% translate 'Not applied' %}</strong>{% else %}{{ application_type.user_instance.get_public_status_display }}</strong>: {{ application_type.user_instance.get_public_status_description }}{% endif %}
24-
</p>
25-
<div class="d-grid gap-4 d-flex justify-content-around">
26-
<a style="flex: inherit" href="{% url 'change_status_application' application_type.user_instance.get_uuid application_type.user_instance.STATUS_CANCELLED %}" onclick="return confirm('{% trans 'Are you sure you want to cancel this application?' %}')" class="btn btn-danger col-4">{% trans 'Cancel application' %}</a>
27-
<a style="flex: inherit" href="{% url 'edit_application' application_type.user_instance.get_uuid %}" class="btn btn-secondary col-4">{% trans 'View application' %}</a>
28-
<a style="flex: inherit" href="{% url 'change_status_application' application_type.user_instance.get_uuid application_type.user_instance.STATUS_CONFIRMED %}" onclick="return confirm('{% blocktrans %}Your {{ application_type }} invitation will be confirmed. This means that all your other applications will be cancelled. Are you sure you want to confirm this one?{% endblocktrans %}')" class="btn btn-primary col-4">{% trans 'Confirm invitation' %}</a>
29-
</div>
30-
</div>
31-
{% else %}
32-
<a href="{% if application_type.user_instance %}{% url 'edit_application' application_type.user_instance.get_uuid %}{% elif application_type.active %}{% url 'apply' %}?type={{ application_type.name }}{% endif %}" class="list-group-item list-group-item-action list-group-item-{{ application_type.user_instance.get_public_status_color|default:theme }} {% if not application_type.user_instance and not application_type.active %}disabled{% endif %}">
33-
<div class="d-flex w-100 justify-content-between">
34-
<h3 class="mb-1">{{ application_type.name }}</h3>
35-
{% if not application_type.user_instance %}
36-
<small>{% if application_type.closed %}{% translate 'Closed' %}{% elif application_type.active %}{% translate 'Open. Closes in' %} {{ application_type.time_left }}{% else %}{% translate 'Closed. Opens in' %} {{ application_type.time_left }}{% endif %}</small>
37-
{% endif %}
38-
</div>
39-
<p>
40-
<strong>{% translate 'Status' %} {% if not application_type.user_instance %}{% translate 'Not applied' %}</strong>{% else %}{{ application_type.user_instance.get_public_status_display }}</strong>: {{ application_type.user_instance.get_public_status_description }}{% endif %}
41-
</p>
42-
</a>
43-
{% endif %}
44-
</div>
45-
{% endfor %}
46-
47-
{% else %}
48-
<p>{% blocktrans %}You confirmed your {{ accepted_application.type.name }} application. See you soon at {{ app_hack }}.{% endblocktrans %}</p>
49-
<div class="list-group rounded-3" style="margin-bottom: 10px">
50-
<div class="list-group-item list-group-item-{{ accepted_application.get_public_status_color|default:theme }}">
51-
<div class="d-flex w-100 justify-content-between">
52-
<h3 class="mb-1">{{ accepted_application.type.name }}</h3>
53-
</div>
54-
<p>
55-
<strong>{% translate 'Status' %} {{ accepted_application.get_public_status_display }}</strong>: {{ accepted_application.get_public_status_description }}
56-
</p>
57-
<div class="d-grid gap-4 d-flex justify-content-around">
58-
<a href="{% url 'change_status_application' accepted_application.get_uuid accepted_application.STATUS_CANCELLED %}" onclick="return confirm('{% trans 'Are you sure you want to cancel this application? You will lose your invitation.' %}')" class="btn btn-danger col-4">{% trans 'Cancel application' %}</a>
59-
<a style="flex: inherit" href="{% url 'edit_application' accepted_application.get_uuid %}" class="btn btn-secondary col-4">{% trans 'View application' %}</a>
60-
</div>
61-
</div>
62-
</div>
13+
{% if user_applications %}
14+
{% include 'application_home/user_applications.html' %}
15+
{% endif %}
16+
{% if apply_types and not user_applications_grouped|get_item:'C' and not user_applications_grouped|get_item:'A' %}
17+
{% include 'application_home/apply.html' %}
6318
{% endif %}
6419

6520
</div>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{% load i18n %}
2+
<div class="mt-2 rounded-3 p-3 bg-contrast">
3+
<h2>{% trans 'Apply' %}</h2>
4+
{% if user_applications %}
5+
<p>{% translate 'You have already applied but if you are interested in any other role, you can apply with them as well' %}</p>
6+
{% endif %}
7+
<ul>
8+
{% for apply_type in apply_types %}
9+
<li>
10+
{{ apply_type.get_description }}<br>
11+
<strong>
12+
{% if apply_type.active %}{% translate 'Closes in' %}{% else %}{% translate 'Opens in' %}{% endif %}{{ apply_type.time_left }}
13+
</strong>
14+
<div class="row justify-content-around mt-3 mb-3">
15+
<div class="col-6 d-grid d-md-block">
16+
<a href="{% url 'apply' %}?type={{ apply_type.name }}" class="btn {% if user_applications %}btn-secondary{% else %}btn-primary{% endif %} col-12">{% trans 'Apply as' %} {{ apply_type.name }}</a>
17+
</div>
18+
</div>
19+
</li>
20+
{% endfor %}
21+
</ul>
22+
23+
</div>

0 commit comments

Comments
 (0)