From 3f1b33b0d33de943fe8cb6dd009a9ed8b83b96fb Mon Sep 17 00:00:00 2001 From: Ali Karbassi Date: Thu, 28 Mar 2019 14:58:29 -0500 Subject: [PATCH] Related to #640 --- .vscode/extensions.json | 39 ++++-- .vscode/settings.json | 84 +++++++------ accounts/forms.py | 224 +++++++++++++++++++++++++++++++++ accounts/views.py | 23 +--- coderdojochi/forms.py | 220 +------------------------------- coderdojochi/old_views.py | 3 +- coderdojochi/views/general.py | 3 +- coderdojochi/views/meetings.py | 12 +- coderdojochi/views/profile.py | 3 +- coderdojochi/views/sessions.py | 3 +- 10 files changed, 319 insertions(+), 295 deletions(-) create mode 100644 accounts/forms.py diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 4ad758e7..6d23f9bb 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,33 @@ { - "recommendations": [ - "ms-python.python", - "PeterJausovec.vscode-docker", - "thebarkman.vscode-djaneiro", - "Zignd.html-css-class-completion" - ] -} \ No newline at end of file + "recommendations": [ + // Python: Linting, Debugging (multi-threaded, remote), Intellisense, + // code formatting, refactoring, unit tests, snippets, and more. + "ms-python.python", + + // Django: Beautiful syntax and scoped snippets + "batisteo.vscode-django", + + // Docker: Adds syntax highlighting, commands, hover tips, and linting + // for Dockerfile and docker-compose files + "PeterJausovec.vscode-docker", + + // IntelliSense for CSS class names in HTML + "Zignd.html-css-class-completion" + + // Auto Rename Tag: Auto rename paired HTML/XML tag + "formulahendry.auto-rename-tag", + + // Beautify: Beautify code in place for VS Code + "hookyqr.beautify", + + // CSS Peek: Allow peeking to css ID and class strings as definitions + // from html files to respective CSS.Allows peek and goto definition. + "pranaygp.vscode-css-peek", + + // HTMLHint: A Static Code Analysis Tool for HTML + "mkaufman.htmlhint", + + // HTML CSS Support: CSS support for HTML documents + "ecmel.vscode-html-css" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 5cbb4296..70fc2471 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,41 +1,49 @@ { - "editor.trimAutoWhitespace": true, - "files.exclude": { - "**/.git": true, - "**/.DS_Store": true, - "**/*.pyc": true, - "**/__pycache__": true, - "staticfiles": true, + "editor.trimAutoWhitespace": true, + "files.exclude": { + "**/.git": true, + "**/.DS_Store": true, + "**/*.pyc": true, + "**/__pycache__": true, + "staticfiles": true, + }, + "files.associations": { + "**/templates/*.html": "django-html", + "**/templates/*": "django-txt", + "**/requirements{/**,*}.{txt,in}": "pip-requirements" + }, + "emmet.includeLanguages": { + "django-html": "html" + }, + "python.linting.enabled": true, + "python.linting.ignorePatterns": [ + ".vscode/*.py", + "**/site-packages/**/*.py", + "**/migrations/**", + ], + "python.linting.pylintEnabled": false, + "python.linting.pep8Enabled": true, + "python.linting.pep8Path": "pycodestyle", + "python.linting.pep8Args": [ + "--max-line-length=120", + ], + "[python]": { + "editor.rulers": [120], + "editor.codeActionsOnSave": { + "source.organizeImports": true }, - "files.associations": { - "*.html": "django-html", + }, + "[git-commit]": { + "editor.rulers": [72], + }, + "[json]": { + "editor.quickSuggestions": { + "strings": true, }, - "python.linting.enabled": true, - "python.linting.ignorePatterns": [ - ".vscode/*.py", - "**/site-packages/**/*.py", - "**/migrations/**", - ], - "python.linting.pylintEnabled": false, - "python.linting.pep8Enabled": true, - "python.linting.pep8Path": "pycodestyle", - "python.linting.pep8Args": [ - "--max-line-length=120", - ], - "[python]": { - "editor.rulers": [120], - }, - "[git-commit]": { - "editor.rulers": [72], - }, - "[json]": { - "editor.quickSuggestions": { - "strings": true, - }, - }, - "[markdown]": { - "editor.wordWrap": "on", - "editor.quickSuggestions": false, - "editor.trimAutoWhitespace": false, - }, -} \ No newline at end of file + }, + "[markdown]": { + "editor.wordWrap": "on", + "editor.quickSuggestions": false, + "editor.trimAutoWhitespace": false, + }, +} diff --git a/accounts/forms.py b/accounts/forms.py new file mode 100644 index 00000000..017122d6 --- /dev/null +++ b/accounts/forms.py @@ -0,0 +1,224 @@ +from django import forms + +import html5.forms.widgets as html5_widgets + +from coderdojochi.models import CDCUser, Guardian, Mentor, RaceEthnicity + + +class CDCModelForm(forms.ModelForm): + # strip leading or trailing whitespace + def _clean_fields(self): + for name, field in list(self.fields.items()): + # value_from_datadict() gets the data from the data dictionaries. + # Each widget type knows how to retrieve its own data, because some + # widgets split data over several HTML fields. + value = field.widget.value_from_datadict( + self.data, + self.files, + self.add_prefix(name) + ) + + try: + if isinstance(field, forms.FileField): + initial = self.initial.get(name, field.initial) + value = field.clean(value, initial) + else: + if isinstance(value, str): + # regex normalizes carriage return + # and cuts them to two at most + value = re.sub(r'\r\n', '\n', value) + value = re.sub(r'\n{3,}', '\n\n', value) + value = field.clean(value.strip()) + else: + value = field.clean(value) + + self.cleaned_data[name] = value + + if hasattr(self, f"clean_{name}"): + value = getattr(self, f"clean_{name}")() + self.cleaned_data[name] = value + + except forms.ValidationError as e: + self.add_error(name, e) + + class Meta: + model = CDCUser + fields = ('first_name', 'last_name') + + +class GuardianForm(CDCModelForm): + phone = forms.CharField( + widget=forms.TextInput( + attrs={ + 'placeholder': 'Phone Number', + 'class': 'form-control' + } + ), + label='Phone Number' + ) + + zip = forms.CharField( + widget=forms.TextInput( + attrs={ + 'placeholder': 'Zip Code', + 'class': 'form-control' + } + ), + label='Zip Code' + ) + + gender = forms.CharField( + widget=forms.TextInput( + attrs={ + 'placeholder': '', + 'class': 'form-control' + } + ), + label='Gender', + required=True + ) + + race_ethnicity = forms.ModelMultipleChoiceField( + widget=forms.SelectMultiple, + queryset=RaceEthnicity.objects.filter(is_visible=True), + required=True + ) + + birthday = forms.CharField( + widget=forms.DateInput( + attrs={ + 'class': 'form-control' + } + ), + required=True + ) + + class Meta: + model = Guardian + fields = ('phone', 'zip', 'gender', 'race_ethnicity', 'birthday') + + +class MentorForm(CDCModelForm): + bio = forms.CharField( + widget=forms.Textarea( + attrs={ + 'placeholder': 'Short Bio', + 'class': 'form-control', + 'rows': 4 + } + ), + label='Short Bio', + required=False + ) + + gender = forms.CharField( + widget=forms.TextInput( + attrs={ + 'placeholder': '', + 'class': 'form-control' + } + ), + label='Gender', + required=True + ) + + race_ethnicity = forms.ModelMultipleChoiceField( + widget=forms.SelectMultiple, + queryset=RaceEthnicity.objects.filter(is_visible=True), + required=True + ) + + birthday = forms.CharField( + widget=html5_widgets.DateInput( + attrs={ + 'class': 'form-control' + } + ), + required=True + ) + + work_place = forms.CharField( + widget=forms.TextInput( + attrs={ + 'placeholder': '', + 'class': 'form-control' + } + ), + label='Work Place', + required=False + ) + + phone = forms.CharField( + widget=forms.TextInput( + attrs={ + 'placeholder': '', + 'class': 'form-control' + } + ), + label='Phone', + required=False + ) + + home_address = forms.CharField( + widget=forms.TextInput( + attrs={ + 'placeholder': '', + 'class': 'form-control' + } + ), + label='Home Address', + required=False + ) + + class Meta: + model = Mentor + fields = ('bio', 'avatar', 'gender', 'race_ethnicity', 'birthday', 'phone', 'home_address', 'work_place') + + def clean_avatar(self): + avatar = self.cleaned_data['avatar'] + + if avatar is None: + return avatar + + try: + w, h = get_image_dimensions(avatar) + + # validate dimensions + max_width = max_height = 1000 + if w > max_width or h > max_height: + raise forms.ValidationError( + f'Please use an image that is {max_width} x {max_height}px or smaller.' + ) + + min_width = min_height = 500 + if w < min_width or h < min_height: + raise forms.ValidationError( + f'Please use an image that is {min_width} x {min_height}px or larger.' + ) + + # validate content type + main, sub = avatar.content_type.split('/') + if ( + not ( + main == 'image' and + sub in ['jpeg', 'pjpeg', 'gif', 'png'] + ) + ): + raise forms.ValidationError( + 'Please use a JPEG, GIF or PNG image.' + ) + + # validate file size + if len(avatar) > (2000 * 1024): + raise forms.ValidationError( + 'Avatar file size may not exceed 2MB.' + ) + + except AttributeError: + """ + Handles case when we are updating the user profile + and do not supply a new avatar + """ + pass + + return avatar diff --git a/accounts/views.py b/accounts/views.py index ac47e69e..8ec2ddf0 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -1,23 +1,14 @@ +from django.contrib import messages from django.contrib.auth.decorators import login_required from django.shortcuts import get_object_or_404, redirect, render from django.utils import timezone from django.utils.decorators import method_decorator from django.views.generic import TemplateView -from coderdojochi.forms import ( - CDCModelForm, - GuardianForm, - MentorForm -) - -from coderdojochi.models import ( - Guardian, - MeetingOrder, - Mentor, - MentorOrder, - Order, - Student, -) +from coderdojochi.models import Guardian, MeetingOrder, Mentor, MentorOrder, Order, Student + +from .forms import CDCModelForm, GuardianForm, MentorForm + class AccountHomeView(TemplateView): @method_decorator(login_required) @@ -40,9 +31,9 @@ def get(self, request): if request.user.role == 'guardian': return self.my_account_guardian(request) - # TODO: upcoming classes needs to be all upcoming classes with a choice to RSVP in dojo page # TODO: upcoming meetings needs to be all upcoming meetings with a choice to RSVP in dojo page + def my_account_mentor(self, request, template_name='account/home_mentor.html'): highlight = ( request.GET['highlight'] if 'highlight' in request.GET else False @@ -138,7 +129,6 @@ def my_account_mentor(self, request, template_name='account/home_mentor.html'): return render(request, template_name, context) - def my_account_guardian(self, request, template_name='account/home_guardian.html'): highlight = ( request.GET['highlight'] if 'highlight' in request.GET else False @@ -212,4 +202,3 @@ def my_account_guardian(self, request, template_name='account/home_guardian.html context['user_form'] = user_form return render(request, template_name, context) - diff --git a/coderdojochi/forms.py b/coderdojochi/forms.py index 78dfc704..182d9c39 100644 --- a/coderdojochi/forms.py +++ b/coderdojochi/forms.py @@ -8,6 +8,7 @@ from django.utils.safestring import mark_safe import html5.forms.widgets as html5_widgets +from accounts.forms import CDCModelForm from coderdojochi.models import CDCUser, Donation, Guardian, Mentor, RaceEthnicity, Session, Student @@ -52,47 +53,6 @@ def _clean_fields(self): self.add_error(name, e) -class CDCModelForm(ModelForm): - # strip leading or trailing whitespace - def _clean_fields(self): - for name, field in list(self.fields.items()): - # value_from_datadict() gets the data from the data dictionaries. - # Each widget type knows how to retrieve its own data, because some - # widgets split data over several HTML fields. - value = field.widget.value_from_datadict( - self.data, - self.files, - self.add_prefix(name) - ) - - try: - if isinstance(field, FileField): - initial = self.initial.get(name, field.initial) - value = field.clean(value, initial) - else: - if isinstance(value, str): - # regex normalizes carriage return - # and cuts them to two at most - value = re.sub(r'\r\n', '\n', value) - value = re.sub(r'\n{3,}', '\n\n', value) - value = field.clean(value.strip()) - else: - value = field.clean(value) - - self.cleaned_data[name] = value - - if hasattr(self, f"clean_{name}"): - value = getattr(self, f"clean_{name}")() - self.cleaned_data[name] = value - - except ValidationError as e: - self.add_error(name, e) - - class Meta: - model = CDCUser - fields = ('first_name', 'last_name') - - class SignupForm(forms.Form): first_name = forms.CharField(max_length=30) last_name = forms.CharField(max_length=30) @@ -106,184 +66,6 @@ def save(self, user): user.save() -class MentorForm(CDCModelForm): - bio = forms.CharField( - widget=forms.Textarea( - attrs={ - 'placeholder': 'Short Bio', - 'class': 'form-control', - 'rows': 4 - } - ), - label='Short Bio', - required=False - ) - - gender = forms.CharField( - widget=forms.TextInput( - attrs={ - 'placeholder': '', - 'class': 'form-control' - } - ), - label='Gender', - required=True - ) - - race_ethnicity = forms.ModelMultipleChoiceField( - widget=forms.SelectMultiple, - queryset=RaceEthnicity.objects.filter(is_visible=True), - required=True - ) - - birthday = forms.CharField( - widget=html5_widgets.DateInput( - attrs={ - 'class': 'form-control' - } - ), - required=True - ) - - work_place = forms.CharField( - widget=forms.TextInput( - attrs={ - 'placeholder': '', - 'class': 'form-control' - } - ), - label='Work Place', - required=False - ) - - phone = forms.CharField( - widget=forms.TextInput( - attrs={ - 'placeholder': '', - 'class': 'form-control' - } - ), - label='Phone', - required=False - ) - - home_address = forms.CharField( - widget=forms.TextInput( - attrs={ - 'placeholder': '', - 'class': 'form-control' - } - ), - label='Home Address', - required=False - ) - - class Meta: - model = Mentor - fields = ('bio', 'avatar', 'gender', 'race_ethnicity', 'birthday', 'phone', 'home_address', 'work_place') - - def clean_avatar(self): - avatar = self.cleaned_data['avatar'] - - if avatar is None: - return avatar - - try: - w, h = get_image_dimensions(avatar) - - # validate dimensions - max_width = max_height = 1000 - if w > max_width or h > max_height: - raise forms.ValidationError( - f'Please use an image that is {max_width} x {max_height}px or smaller.' - ) - - min_width = min_height = 500 - if w < min_width or h < min_height: - raise forms.ValidationError( - f'Please use an image that is {min_width} x {min_height}px or larger.' - ) - - # validate content type - main, sub = avatar.content_type.split('/') - if ( - not ( - main == 'image' and - sub in ['jpeg', 'pjpeg', 'gif', 'png'] - ) - ): - raise forms.ValidationError( - 'Please use a JPEG, GIF or PNG image.' - ) - - # validate file size - if len(avatar) > (2000 * 1024): - raise forms.ValidationError( - 'Avatar file size may not exceed 2MB.' - ) - - except AttributeError: - """ - Handles case when we are updating the user profile - and do not supply a new avatar - """ - pass - - return avatar - - -class GuardianForm(CDCModelForm): - phone = forms.CharField( - widget=forms.TextInput( - attrs={ - 'placeholder': 'Phone Number', - 'class': 'form-control' - } - ), - label='Phone Number' - ) - - zip = forms.CharField( - widget=forms.TextInput( - attrs={ - 'placeholder': 'Zip Code', - 'class': 'form-control' - } - ), - label='Zip Code' - ) - - gender = forms.CharField( - widget=forms.TextInput( - attrs={ - 'placeholder': '', - 'class': 'form-control' - } - ), - label='Gender', - required=True - ) - - race_ethnicity = forms.ModelMultipleChoiceField( - widget=forms.SelectMultiple, - queryset=RaceEthnicity.objects.filter(is_visible=True), - required=True - ) - - birthday = forms.CharField( - widget=html5_widgets.DateInput( - attrs={ - 'class': 'form-control' - } - ), - required=True - ) - - class Meta: - model = Guardian - fields = ('phone', 'zip', 'gender', 'race_ethnicity', 'birthday') - - class StudentForm(CDCModelForm): first_name = forms.CharField( widget=forms.TextInput( diff --git a/coderdojochi/old_views.py b/coderdojochi/old_views.py index c7c4aaca..5dc808da 100644 --- a/coderdojochi/old_views.py +++ b/coderdojochi/old_views.py @@ -22,11 +22,12 @@ from django.views.generic import TemplateView import arrow +from accounts.forms import GuardianForm, MentorForm from dateutil.relativedelta import relativedelta from icalendar import Calendar, Event, vText from paypal.standard.forms import PayPalPaymentsForm -from coderdojochi.forms import CDCModelForm, ContactForm, DonationForm, GuardianForm, MentorForm, StudentForm +from coderdojochi.forms import CDCModelForm, ContactForm, DonationForm, StudentForm from coderdojochi.models import ( Donation, Equipment, diff --git a/coderdojochi/views/general.py b/coderdojochi/views/general.py index f40195e6..f6a1ac99 100644 --- a/coderdojochi/views/general.py +++ b/coderdojochi/views/general.py @@ -23,10 +23,11 @@ from django.views.generic import TemplateView, View import arrow +from accounts.forms import GuardianForm, MentorForm from icalendar import Calendar, Event, vText from paypal.standard.forms import PayPalPaymentsForm -from coderdojochi.forms import CDCModelForm, ContactForm, DonationForm, GuardianForm, MentorForm, StudentForm +from coderdojochi.forms import CDCModelForm, ContactForm, DonationForm, StudentForm from coderdojochi.models import ( Donation, Equipment, diff --git a/coderdojochi/views/meetings.py b/coderdojochi/views/meetings.py index 4c79b9c8..60dc17d2 100644 --- a/coderdojochi/views/meetings.py +++ b/coderdojochi/views/meetings.py @@ -23,19 +23,11 @@ from django.views.generic import DetailView, ListView, RedirectView, TemplateView, View import arrow +from accounts.forms import GuardianForm, MentorForm from icalendar import Calendar, Event, vText from paypal.standard.forms import PayPalPaymentsForm -from coderdojochi.forms import ( - CDCForm, - CDCModelForm, - ContactForm, - DonationForm, - GuardianForm, - MentorForm, - SignupForm, - StudentForm, -) +from coderdojochi.forms import CDCForm, CDCModelForm, ContactForm, DonationForm, SignupForm, StudentForm from coderdojochi.mixins import RoleRedirectMixin from coderdojochi.models import ( Donation, diff --git a/coderdojochi/views/profile.py b/coderdojochi/views/profile.py index ce296d79..6b697388 100644 --- a/coderdojochi/views/profile.py +++ b/coderdojochi/views/profile.py @@ -22,10 +22,11 @@ from django.views.generic import TemplateView import arrow +from accounts.forms import GuardianForm, MentorForm from icalendar import Calendar, Event, vText from paypal.standard.forms import PayPalPaymentsForm -from coderdojochi.forms import CDCModelForm, ContactForm, DonationForm, GuardianForm, MentorForm, StudentForm +from coderdojochi.forms import CDCModelForm, ContactForm, DonationForm, StudentForm from coderdojochi.models import ( Donation, Equipment, diff --git a/coderdojochi/views/sessions.py b/coderdojochi/views/sessions.py index 00b99abb..ba83fbc4 100644 --- a/coderdojochi/views/sessions.py +++ b/coderdojochi/views/sessions.py @@ -23,11 +23,12 @@ from django.views.generic import RedirectView, TemplateView, View import arrow +from accounts.forms import GuardianForm, MentorForm from dateutil.relativedelta import relativedelta from icalendar import Calendar, Event, vText from paypal.standard.forms import PayPalPaymentsForm -from coderdojochi.forms import CDCModelForm, ContactForm, DonationForm, GuardianForm, MentorForm, StudentForm +from coderdojochi.forms import CDCModelForm, ContactForm, DonationForm, StudentForm from coderdojochi.mixins import RoleRedirectMixin from coderdojochi.models import ( Donation,