Skip to content

Commit 4abe491

Browse files
Merge branch 'master' into friends
2 parents bbdf08e + 0b01a9d commit 4abe491

File tree

203 files changed

+12149
-412
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

203 files changed

+12149
-412
lines changed

.github/workflows/docker.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: DockerHub CI
2+
3+
on:
4+
workflow_run:
5+
workflows: [ "Django CI" ]
6+
branches: [ master ]
7+
types: [completed]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
13+
steps:
14+
-
15+
name: Checkout
16+
uses: actions/checkout@v2
17+
-
18+
name: Login to Docker Hub
19+
uses: docker/login-action@v1
20+
with:
21+
username: ${{ secrets.DOCKER_HUB_USERNAME }}
22+
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
23+
-
24+
name: Set up Docker Buildx
25+
uses: docker/setup-buildx-action@v1
26+
-
27+
name: Build and push
28+
uses: docker/build-push-action@v2
29+
with:
30+
context: .
31+
file: ./Dockerfile
32+
builder: ${{ steps.buildx.outputs.name }}
33+
push: true
34+
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPO }}:latest
35+
cache-from: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPO }}:buildcache
36+
cache-to: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPO }}:buildcache,mode=max

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ env/
66
.idea
77
__pycache__/
88
.DS_Store
9+
staticfiles/
910

1011
/files/*
1112
!files/.keep
13+
14+
/db/*
15+
!db/.keep

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.

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM python:3.9.13
2+
RUN apt-get update
3+
RUN apt-get install -y cron && touch /var/log/cron.log
4+
RUN pip install --upgrade pip
5+
WORKDIR /code
6+
7+
COPY requirements.txt /code/
8+
RUN pip3 install -r requirements.txt && pip install gunicorn
9+
10+
COPY . /code/
11+
12+
CMD ["sh", "./run.sh"]

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- Email sign up ✉️
1212
- Email verification 📨
1313
- Forgot password 🤔
14+
- Ip block on failed login tries & ip blocklist ✋ (Optional)
1415
- Dark mode 🌚 🌝 Light mode (Optional)
1516

1617
## Development

app/hackathon_variables.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
HACKATHON_NAME = 'HackUPC'
2-
HACKATHON_DESCRIPTION = 'Join us for BarcelonaTech\'s hackathon. 36h. 29th April - 1st May.'
2+
HACKATHON_DESCRIPTION = 'Join us for BarcelonaTech\'s hackathon. 36h.'
33
HACKATHON_ORG = 'Hackers@UPC'
44

55
HACKATHON_CONTACT_EMAIL = '[email protected]'
@@ -16,6 +16,9 @@
1616
REGEX_HACKATHON_ORGANIZER_EMAIL = "^.*@hackupc\.com$"
1717
HACKATHON_ORGANIZER_EMAILS = []
1818
APP_NAME = 'MyHackUPC'
19-
APP_EMAIL = ''
19+
APP_EMAIL = 'MyHackUPC <[email protected]>'
20+
21+
# (OPTIONAL) Send 500 errors to email while on production mode
22+
HACKATHON_DEV_EMAILS = ['[email protected]', ]
2023

2124
SUPPORTED_RESUME_EXTENSIONS = ['.pdf']

app/log.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import logging
2+
from copy import copy
3+
4+
from django.core.mail import EmailMultiAlternatives
5+
from django.views.debug import ExceptionReporter
6+
7+
from app import settings
8+
9+
10+
class HackathonDevEmailHandler(logging.Handler):
11+
"""An exception log handler that emails log entries to site hackathon devs.
12+
13+
If the request is passed as the first argument to the log record,
14+
request data will be provided in the email report.
15+
This is replicated from Django log to use the default backend properly.
16+
17+
See: https://docs.djangoproject.com/en/1.11/howto/error-reporting/
18+
"""
19+
20+
def emit(self, record):
21+
22+
try:
23+
request = record.request
24+
except Exception:
25+
request = None
26+
27+
subject = '%s: %s' % (
28+
record.levelname,
29+
record.getMessage()
30+
)
31+
subject = subject.replace('\n', '\\n').replace('\r', '\\r')
32+
33+
# Since we add a nicely formatted traceback on our own, create a copy
34+
# of the log record without the exception data.
35+
no_exc_record = copy(record)
36+
no_exc_record.exc_info = None
37+
no_exc_record.exc_text = None
38+
39+
if record.exc_info:
40+
exc_info = record.exc_info
41+
else:
42+
exc_info = (None, record.getMessage(), None)
43+
if settings.HACKATHON_DEV_EMAILS:
44+
reporter = ExceptionReporter(request, is_email=True, *exc_info)
45+
message = "%s\n\n%s" % (self.format(no_exc_record), reporter.get_traceback_text())
46+
html_message = reporter.get_traceback_html()
47+
msg = EmailMultiAlternatives(subject,
48+
message,
49+
'server@' + settings.HACKATHON_DOMAIN,
50+
settings.HACKATHON_DEV_EMAILS)
51+
msg.attach_alternative(html_message, 'text/html')
52+
msg.send(fail_silently=True)

app/mixins.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import copy
22

33
from django import forms
4+
from django.contrib.auth.mixins import PermissionRequiredMixin as OverridePermissionRequiredMixin
45
from django.forms import model_to_dict
56
from django.utils.safestring import mark_safe
67
from django.utils.translation import gettext_lazy as _
78

89

910
class TabsViewMixin:
10-
def get_current_tabs(self):
11+
def get_current_tabs(self, **kwargs):
1112
return []
1213

1314
def get_back_url(self):
1415
return None
1516

1617
def get_context_data(self, **kwargs):
1718
context = super(TabsViewMixin, self).get_context_data(**kwargs)
18-
tabs = self.get_current_tabs()
19+
tabs = self.get_current_tabs(**kwargs)
1920
new_tabs = []
2021
for tab in tabs:
2122
new_tab = {'title': tab[0], 'url': tab[1], 'needs_action': tab[2] if len(tab) > 2 else None,
@@ -112,3 +113,13 @@ def get_fields(self):
112113
del visible[field['field'].auto_id]
113114
list_fields['visible'] = visible
114115
return result
116+
117+
118+
class PermissionRequiredMixin(OverridePermissionRequiredMixin):
119+
def get_permission_required(self):
120+
permissions = super().get_permission_required()
121+
if isinstance(permissions, dict):
122+
permissions = permissions.get(self.request.method, [])
123+
if isinstance(permissions, str):
124+
permissions = [permissions, ]
125+
return permissions

0 commit comments

Comments
 (0)