diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..2b6cc798 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,7 @@ +[run] +omit = + # Omit Django migration files + */migrations/* + + # Omit tests themselves + */test*.py \ No newline at end of file diff --git a/.github/workflows/lowfat.yml b/.github/workflows/lowfat.yml new file mode 100644 index 00000000..027666fe --- /dev/null +++ b/.github/workflows/lowfat.yml @@ -0,0 +1,46 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: LowFAT + +on: + push: + branches: [ master, dev ] + pull_request: + branches: [ master, dev ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8] + django-version: [1.11] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install django~=${{ matrix.django-version }} + + - name: Test with Django test command + run: | + python manage.py test + + - name: Test database migration and fixtures + run: | + python manage.py migrate + python manage.py loaddata fixtures/demo.json + + - name: Lint with Pylint + run: | + python -m pylint lowfat diff --git a/.gitignore b/.gitignore index 4aba05bf..6361beac 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ backups/ *.log.* # Python runtime, setup and testing +.coverage .mypy_cache/ __pycache__ env/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7fb25aae..00000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -dist: xenial # required for Python >= 3.7 - -language: python - -python: - - "3.4" - - "3.5" - - "3.5-dev" # 3.5 development branch - - "3.6" - - "3.6-dev" # 3.6 development branch -# Not supported by Django < 1.11.17 - - "3.7" - - "3.7-dev" # 3.7 development branch - -cache: pip - -env: - - DJANGO_VERSION=1.11 # Unsupported after April 2020 -# Not supported by some dependencies -# - DJANGO_VERSION=2.1 -# - DJANGO_VERSION=2.2 - -install: - - pip install django~=$DJANGO_VERSION # Accept any version in series - - pip install -r requirements.txt - -script: - - "python manage.py test" - - "python manage.py migrate && python manage.py loaddata fixtures/demo.json" - - "python -m pylint lowfat" \ No newline at end of file diff --git a/lowfat/forms.py b/lowfat/forms.py index c29cb0ad..54f1648b 100644 --- a/lowfat/forms.py +++ b/lowfat/forms.py @@ -1,6 +1,7 @@ from datetime import datetime, date +import textwrap -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.forms import ( BooleanField, CharField, @@ -46,7 +47,7 @@ def __init__(self, *args, **kwargs): self.is_staff = kwargs.pop("is_staff", False) # Set up Garlic attribute to persistent data - super(GarlicForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.attrs = { 'data_persist': "garlic", @@ -84,7 +85,7 @@ class Meta: required_css_class = 'form-field-required' def __init__(self, *args, **kwargs): - super(ClaimantForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.helper.layout = Layout( Fieldset( @@ -200,7 +201,7 @@ class Meta: required_css_class = 'form-field-required' def __init__(self, *args, **kwargs): - super(FellowForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.helper.layout = Layout( Fieldset( @@ -348,7 +349,7 @@ def clean_end_date(self): return self.cleaned_data['end_date'] def __init__(self, *args, **kwargs): - super(FundForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.helper.layout = Layout( Fieldset( @@ -561,7 +562,7 @@ def clean_end_date(self): return self.cleaned_data['end_date'] def __init__(self, *args, **kwargs): - super(FundPublicForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.helper.layout = Layout( Fieldset( @@ -671,7 +672,7 @@ class Meta: required_css_class = 'form-field-required' def __init__(self, *args, **kwargs): - super(FundGDPRForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.helper.layout = Layout( Fieldset( @@ -718,7 +719,7 @@ class Meta: email = CharField(widget=Textarea, required=False) def __init__(self, *args, **kwargs): - super(FundReviewForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.helper.layout = Layout( Fieldset( @@ -754,7 +755,7 @@ class FundImportForm(Form): csv = FileField() def __init__(self, *args, **kwargs): - super(FundImportForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.attrs = { @@ -837,14 +838,23 @@ class Meta: required_css_class = 'form-field-required' def __init__(self, *args, **kwargs): - super(ExpenseForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.helper.layout = Layout( Fieldset( '', 'fund', - HTML("
If your funding request isn't on the drop down menu above please email us."), - HTML("Fellowship Programme's terms and conditions applies to your request. Please follow the guidelines at How to apply for, and claim, expenses section of Fellowship Programme's terms and conditions."), + HTML("If your funding request isn't on the drop down menu above please email us.
"), + HTML(textwrap.dedent("""\ ++ Before submitting your expense claim: +
We prefer to receive links to Google Docs (tips here), Microsoft Office 365 document or any other online live collaborative document platform you like to use. Posts published somewhere already, e.g. your personal blog, are welcome as well.
"), + HTML( + "For guidance on writing a blog post for the SSI website, please refer to the" + " Guides for content contributors.
" + "We prefer to receive links to Google Docs" + " (tips here)," + " Microsoft Office 365 document" + " or any other online live collaborative document platform you like to use." + " Posts published somewhere already, e.g. your personal blog, are welcome as well.
"), 'draft_url', 'success_reported', 'notes_from_author', @@ -1083,7 +1100,7 @@ class Meta: email = CharField(widget=Textarea, required=False) def __init__(self, *args, **kwargs): - super(BlogReviewForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.helper.layout = Layout( Fieldset( @@ -1105,4 +1122,5 @@ def __init__(self, *args, **kwargs): ) ) - self.fields['reviewer'].queryset = User.objects.filter(is_staff=True) + self.fields['reviewer'].queryset = get_user_model().objects.filter( + is_staff=True) diff --git a/lowfat/mail.py b/lowfat/mail.py index 730e215b..64a5446d 100644 --- a/lowfat/mail.py +++ b/lowfat/mail.py @@ -153,15 +153,18 @@ def review_notification(email_url, user_email, context, mail, copy_to_staffs=Fal plain_text = html2text_fix(html) mail.justification = plain_text + cc_addresses = [config.FELLOWS_MANAGEMENT_EMAIL] + if copy_to_gatekeeper: + cc_addresses.append(config.WEBSITE_GATEKEEPER) + # Email to claimant msg = EmailMultiAlternatives( flatemail.title, plain_text, mail.sender.email, user_email, - cc=[config.WEBSITE_GATEKEEPER_EMAIL] if copy_to_gatekeeper else None, - bcc=ast.literal_eval(config.STAFFS_EMAIL) if copy_to_staffs else None, - reply_to=[config.FELLOWS_MANAGEMENT_EMAIL] + cc=cc_addresses, + bcc=ast.literal_eval(config.STAFFS_EMAIL) if copy_to_staffs else None ) msg.attach_alternative(html, "text/html") msg.send(fail_silently=False) diff --git a/lowfat/management/commands/load2018applications.py b/lowfat/management/commands/load2018applications.py index 95aa6dd6..f26347fd 100644 --- a/lowfat/management/commands/load2018applications.py +++ b/lowfat/management/commands/load2018applications.py @@ -1,6 +1,7 @@ import pandas as pd -from django.contrib.auth.models import User, BaseUserManager +from django.contrib.auth import get_user_model +from django.contrib.auth.models import BaseUserManager from django.core.management.base import BaseCommand from lowfat.models import Claimant @@ -63,7 +64,7 @@ def handle(self, *args, **options): success_list.append(index) if received_offer: - new_user = User.objects.create_user( + new_user = get_user_model().objects.create_user( username=applicant.slug, email=applicant.email, password=user_manager.make_random_password(), diff --git a/lowfat/management/commands/load2019applications.py b/lowfat/management/commands/load2019applications.py index d35b0bfe..f739b1b1 100644 --- a/lowfat/management/commands/load2019applications.py +++ b/lowfat/management/commands/load2019applications.py @@ -1,6 +1,7 @@ import pandas as pd -from django.contrib.auth.models import User, BaseUserManager +from django.contrib.auth import get_user_model +from django.contrib.auth.models import BaseUserManager from django.core.management.base import BaseCommand from django.db import IntegrityError @@ -63,7 +64,7 @@ def handle(self, *args, **options): success_list.append(index) if received_offer: - new_user = User.objects.create_user( + new_user = get_user_model().objects.create_user( username=applicant.slug, email=applicant.email, password=user_manager.make_random_password(), @@ -85,7 +86,7 @@ def handle(self, *args, **options): success_list.append(index) if received_offer: - new_user = User.objects.create_user( + new_user = get_user_model().objects.create_user( username=applicant.slug, email=applicant.email, password=user_manager.make_random_password(), diff --git a/lowfat/models.py b/lowfat/models.py index 62f76740..f18a7967 100644 --- a/lowfat/models.py +++ b/lowfat/models.py @@ -469,7 +469,7 @@ def slug_generator(self): return slug - def save(self, *args, **kwargs): # pylint: disable=arguments-differ + def save(self, *args, **kwargs): # pylint: disable=signature-differs if not self.id: self.inauguration_grant_expiration = date( date.today().year + 2, @@ -483,7 +483,7 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ self.website = fix_url(self.website) self.website_feed = fix_url(self.website_feed) - super(Claimant, self).save(*args, **kwargs) + super().save(*args, **kwargs) def update_latlon(self): geolocator = Nominatim( @@ -744,7 +744,7 @@ def remove(self): self.status = "X" self.save() - def save(self, *args, **kwargs): # pylint: disable=arguments-differ + def save(self, *args, **kwargs): # pylint: disable=signature-differs if not self.pk: self.grant = config.GRANTS_DEFAULT if date.today() < self.claimant.inauguration_grant_expiration: @@ -761,7 +761,7 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ self.url = fix_url(self.url) - super(Fund, self).save(*args, **kwargs) + super().save(*args, **kwargs) def update_latlon(self): geolocator = Nominatim( @@ -978,7 +978,7 @@ def remove(self): self.status = "X" self.save() - def save(self, *args, **kwargs): # pylint: disable=arguments-differ + def save(self, *args, **kwargs): # pylint: disable=signature-differs if self.pk is None: previous_expenses = Expense.objects.filter(fund=self.fund).order_by("-pk") if previous_expenses: @@ -1003,7 +1003,7 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ INVOICE_HASH.hexdigest()[5:9] ) - super(Expense, self).save(*args, **kwargs) + super().save(*args, **kwargs) def link(self): if self.access_token: @@ -1102,14 +1102,14 @@ def remove(self): self.status = "X" self.save() - def save(self, *args, **kwargs): # pylint: disable=arguments-differ + def save(self, *args, **kwargs): # pylint: disable=signature-differs self.draft_url = fix_url(self.draft_url) self.published_url = fix_url(self.published_url) self.tweet_url = fix_url(self.tweet_url) if self.published_url: self.status = 'P' - super(Blog, self).save(*args, **kwargs) + super().save(*args, **kwargs) def __str__(self): return "{}".format(self.draft_url) diff --git a/lowfat/settings.py b/lowfat/settings.py index 51a5f19f..079a1591 100644 --- a/lowfat/settings.py +++ b/lowfat/settings.py @@ -15,7 +15,7 @@ URL_SRC = "https://github.com/softwaresaved/lowfat" -VERSION = "1.18.2" +VERSION = "1.19.0" SETTINGS_EXPORT = [ 'URL_SRC', diff --git a/lowfat/templates/lowfat/base.html b/lowfat/templates/lowfat/base.html index d167a116..56b8c50c 100644 --- a/lowfat/templates/lowfat/base.html +++ b/lowfat/templates/lowfat/base.html @@ -45,10 +45,10 @@ {% endfor %} - {{ form.media }} + {% if form %}{{ form.media }}{% endif %} -