diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml new file mode 100644 index 0000000..9a5c93f --- /dev/null +++ b/.github/workflows/django.yml @@ -0,0 +1,49 @@ +name: Django CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +# Prevent multiple workflows with same branch/pull_request +concurrency: + group: ${{ github.ref_name }} + cancel-in-progress: true + +env: + PIPENV_VENV_IN_PROJECT: 1 + ENV: development + SECRET_KEY: NOT SET + DJANGO_SUPERUSER_USERNAME: admin + DJANGO_SUPERUSER_PASSWORD: Django123 + DJANGO_SUPERUSER_EMAIL: admin@example.com + DJANGO_SETTINGS_MODULE: root.settings + +jobs: + job_build_django: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9.2' # Closest version to 3.7.3 available. + + - name: Install dependencies + run: | + python -V + PY=$(which python) + echo $PY + python -m pip install --upgrade pip + python -m pip install pipenv + python -m pipenv install --python $PY + + - name: Verify migrations + run: | + python -m pipenv run python manage.py makemigrations + python -m pipenv run python manage.py migrate + + - name: Run pylint + run: python -m pipenv run pylint ./run-pylint.sh diff --git a/.pylintrc b/.pylintrc index 2435b4e..b1f0b94 100644 --- a/.pylintrc +++ b/.pylintrc @@ -9,6 +9,10 @@ argument-rgx=(([a-z_]+[a-z0-9_]*)|(_[a-z0-9_]*)|(__[a-z][a-z0-9_]+__))$ method-rgx=(([a-z_]+[a-z0-9_]*)|(_[a-z0-9_]*)|(__[a-z][a-z0-9_]+__))$ attr-rgx=(([a-z_]+[a-z0-9_]*)|(_[a-z0-9_]*)|(__[a-z][a-z0-9_]+__))$ +[pylint-django] +django-settings-module=root.settings + + [MESSAGES CONTROL] disable= @@ -22,6 +26,8 @@ disable= too-many-locals, too-many-statements, unsubscriptable-object, # https://github.com/PyCQA/pylint/issues/1498 + fixme, + ; django-not-configured, # django-settings-module=root.settings doesn't work [VARIABLES] diff --git a/infoscreen/admin.py b/infoscreen/admin.py index 59d9934..a97bae8 100644 --- a/infoscreen/admin.py +++ b/infoscreen/admin.py @@ -8,10 +8,10 @@ class ScreenAdmin(root_models.CustomBaseAdmin): list_display = ['name'] ordering = [] list_filter = ['images', 'videos'] - filter_horizontal = ['images','videos'] + filter_horizontal = ['images', 'videos'] search_fields = ['name', 'slug'] - - + + class ScreenHasImageAdmin(admin.ModelAdmin): list_display = ['screen', 'image', 'nr'] ordering = ['screen', 'nr'] @@ -36,8 +36,7 @@ class VideoAdmin(root_models.CustomBaseAdmin): search_fields = ['name'] - admin.site.register(infoscreen_models.Screen, ScreenAdmin) # admin.site.register(infoscreen_models.ScreenHasImage, ScreenHasImageAdmin) admin.site.register(infoscreen_models.Image, ImageAdmin) -admin.site.register(infoscreen_models.Video, VideoAdmin) \ No newline at end of file +admin.site.register(infoscreen_models.Video, VideoAdmin) diff --git a/infoscreen/apps.py b/infoscreen/apps.py index c7b5062..b88adf5 100644 --- a/infoscreen/apps.py +++ b/infoscreen/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig +from django.core import management class InfoScreenConfig(AppConfig): diff --git a/infoscreen/constants/choices.py b/infoscreen/constants/choices.py index 82a8088..d7b90bf 100644 --- a/infoscreen/constants/choices.py +++ b/infoscreen/constants/choices.py @@ -1,4 +1,3 @@ - class Weekday: MON = 0 TUE = 1 @@ -7,13 +6,5 @@ class Weekday: FRI = 4 SAT = 5 SUN = 6 - - CHOICES = [ - (MON, "Mandag"), - (TUE, "Tirsdag"), - (WED, "Onsdag"), - (THU, "Torsdag"), - (FRI, "Fredag"), - (SAT, "Lørdag"), - (SUN, "Søndag") - ] \ No newline at end of file + + CHOICES = [(MON, 'Mandag'), (TUE, 'Tirsdag'), (WED, 'Onsdag'), (THU, 'Torsdag'), (FRI, 'Fredag'), (SAT, 'Lørdag'), (SUN, 'Søndag')] diff --git a/infoscreen/models/color.py b/infoscreen/models/color.py index 69f246a..a60fe09 100644 --- a/infoscreen/models/color.py +++ b/infoscreen/models/color.py @@ -1,31 +1,32 @@ +import re +from django.core.exceptions import ValidationError from django.db import models - from root import models as root_models class Color(root_models.CustomBaseModel): name = models.CharField(max_length=200, null=True, blank=True, verbose_name='navn') - hex = models.CharField(max_length=6, null=True, blank=True, verbose_name="hex", help_text="Fargekode i hex (6 symboler)") + hex = models.CharField(max_length=6, null=True, blank=True, verbose_name='hex', help_text='Fargekode i hex (6 symboler)') class Meta: ordering = [] verbose_name = 'farge' verbose_name_plural = 'farger' - + def __str__(self): - return f"{self.get_name()}" - + return f'{self.get_name()}' + def get_name(self): if self.name: return self.name return self.hex - + def as_css(self): # TODO: handle COLOR_RANDOM # if self.name == root_constants.COLOR_RANDOM: - # return - return f"#{self.hex}" - + # return + return f'#{self.hex}' + def clean(self, *args, **kwargs): super().clean(*args, **kwargs) errors = {} @@ -34,8 +35,7 @@ def clean(self, *args, **kwargs): errors['hex'] = 'Ugyldig format. Bruk 0-9 og A-F' if errors: raise ValidationError(errors) - + def save(self, *args, **kwargs): self.clean() super().save(*args, **kwargs) - diff --git a/infoscreen/models/fields/__init__.py b/infoscreen/models/fields/__init__.py index f4073f3..700b04c 100644 --- a/infoscreen/models/fields/__init__.py +++ b/infoscreen/models/fields/__init__.py @@ -1,2 +1,2 @@ from .restricted_file import ContentTypeRestrictedFileField -# from .weekday import WeekdayField \ No newline at end of file +# from .weekday import WeekdayField diff --git a/infoscreen/models/fields/restricted_file.py b/infoscreen/models/fields/restricted_file.py index 30589c0..ad37c05 100644 --- a/infoscreen/models/fields/restricted_file.py +++ b/infoscreen/models/fields/restricted_file.py @@ -1,4 +1,7 @@ from django.db.models import FileField +from django import forms +from django.template.defaultfilters import filesizeformat +from django.utils.translation import gettext_lazy as _ class ContentTypeRestrictedFileField(FileField): @@ -15,14 +18,16 @@ class ContentTypeRestrictedFileField(FileField): 250MB - 214958080 500MB - 429916160 """ - def __init__(self, content_types=None, max_upload_size=5242880,*args, **kwargs): + + def __init__(self, *args, content_types=None, max_upload_size=5242880, **kwargs): super().__init__(*args, **kwargs) self.content_types = content_types self.max_upload_size = max_upload_size - def clean(self, *args, **kwargs): + def clean(self, *args, **kwargs): data = super().clean(*args, **kwargs) + # pylint: disable=protected-access file = data.file try: content_type = file.content_type @@ -32,6 +37,6 @@ def clean(self, *args, **kwargs): else: raise forms.ValidationError(_('Filetype ikke støttet.')) except AttributeError: - pass + pass - return data \ No newline at end of file + return data diff --git a/infoscreen/models/fields/weekday.py b/infoscreen/models/fields/weekday.py index 16d3c13..703d88d 100644 --- a/infoscreen/models/fields/weekday.py +++ b/infoscreen/models/fields/weekday.py @@ -1,15 +1,15 @@ # from django.db.models import IntegerField -# +# # class WeekDayField(IntegerField): -# +# # def __init__(self, *args, **kwargs): # kwargs['choices'] = self.DAYS # super().__init__(*args, **kwargs) -# -# def clean(self, *args, **kwargs): +# +# def clean(self, *args, **kwargs): # data = super().clean(*args, **kwargs) # try: # if data and 0 > data > 6: raise forms.ValidationError(_('Ikke eksisterende ')) # except AttributeError: -# pass +# pass # return data diff --git a/infoscreen/models/image.py b/infoscreen/models/image.py index 81a4e81..e74aa86 100644 --- a/infoscreen/models/image.py +++ b/infoscreen/models/image.py @@ -2,9 +2,10 @@ from root import models as root_models + class Image(root_models.CustomBaseModel): name = models.CharField(max_length=140, null=True, blank=True) url = models.URLField(null=False, blank=False) - + def __str__(self): - return f"{self.name}" \ No newline at end of file + return f'{self.name}' diff --git a/infoscreen/models/screen.py b/infoscreen/models/screen.py index dcb4576..230a803 100644 --- a/infoscreen/models/screen.py +++ b/infoscreen/models/screen.py @@ -5,19 +5,19 @@ class Screen(root_models.CustomBaseModel): name = models.CharField(max_length=140, unique=True, null=True, blank=True) slug = models.CharField(max_length=140, unique=True, null=False, blank=False) - + # images = models.ManyToManyField('Image', through='ScreenHasImage') images = models.ManyToManyField('infoscreen.Image', blank=True) videos = models.ManyToManyField('infoscreen.Video', blank=True) def __str__(self): - return f"{self.name}" - + return f'{self.name}' + # class ScreenHasImage(models.Model): # screen = models.ForeignKey('infoscreen.Screen', on_delete=models.CASCADE, null=False, blank=False) # image = models.ForeignKey('infoscreen.Image', on_delete=models.CASCADE, null=False, blank=False) # nr = models.IntegerField(null=False, blank=False) -# +# # class Meta: # unique_together = ['screen', 'image'] diff --git a/infoscreen/models/tag.py b/infoscreen/models/tag.py index b863c50..833cd4f 100644 --- a/infoscreen/models/tag.py +++ b/infoscreen/models/tag.py @@ -1,45 +1,64 @@ # imports -import re - from django.db import models -from django.db.models import Q -from django.core.exceptions import NON_FIELD_ERRORS, ValidationError - from root import models as root_models # End: imports ----------------------------------------------------------------- - + class Tag(root_models.CustomBaseModel): - name = models.CharField(max_length=200, unique=True, null=False, blank=False, verbose_name='navn', help_text="En vilkårlig egenskap til en plante. (Tips: Du kan prefikse tags med kolon ':', f.eks. 'familie:fiola' )") - bg = models.ForeignKey('infoscreen.Color', on_delete=models.SET_NULL, null=True, blank=True, related_name='tag_bg', verbose_name='bakgrunnsfarge') - font = models.ForeignKey('infoscreen.Color', on_delete=models.SET_NULL, null=True, blank=True, related_name='tag_font', verbose_name='skriftfarge') - group = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='children', verbose_name='gruppe') - + name = models.CharField( + max_length=200, + unique=True, + null=False, + blank=False, + verbose_name='navn', + ) + bg = models.ForeignKey( + 'infoscreen.Color', + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='tag_bg', + verbose_name='bakgrunnsfarge', + ) + font = models.ForeignKey( + 'infoscreen.Color', + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='tag_font', + verbose_name='skriftfarge', + ) + group = models.ForeignKey( + 'self', + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='children', + verbose_name='gruppe', + ) + class Meta: ordering = [] verbose_name = 'tag' verbose_name_plural = 'tags' - + def __str__(self): - return f"{self.full_name()}" - + return f'{self.full_name()}' + def full_name(self): if self.group: - return f"{self.group.full_name()} :: {self.name}" - return f"{self.name}" - - + return f'{self.group.full_name()} :: {self.name}' + return f'{self.name}' + def color_list(self): """Hierarchical list of colors from least to most significant""" colors = [] - if self.tag_group: - colors += self.tag_group.color_list() - if self.color: - colors.append(self.color) + if self.group: + colors += self.group.color_list() + if self.bg: + colors.append(self.bg) return colors - + def color_list_css(self): return [color.as_css() for color in self.color_list()] - - \ No newline at end of file diff --git a/infoscreen/models/video.py b/infoscreen/models/video.py index 091d489..bfc8e8c 100644 --- a/infoscreen/models/video.py +++ b/infoscreen/models/video.py @@ -1,15 +1,15 @@ from django.db import models +from root import models as root_models from ..utils.__init__ import parse_url -from root import models as root_models class Video(root_models.CustomBaseModel): name = models.CharField(max_length=140, null=True, blank=True) youtube_code = models.CharField(max_length=140, null=False, blank=False) - + def __str__(self): - return f"{self.name}" - + return f'{self.name}' + def save(self, *args, **kwargs): self.youtube_code = parse_url(self.youtube_code) super().save(*args, **kwargs) diff --git a/infoscreen/templates/infoscreen/screen.html b/infoscreen/templates/infoscreen/screen.html index f10b95e..dd3c84f 100644 --- a/infoscreen/templates/infoscreen/screen.html +++ b/infoscreen/templates/infoscreen/screen.html @@ -41,3 +41,4 @@ {% endblock %} + diff --git a/infoscreen/templatetags/infoscreen_tags.py b/infoscreen/templatetags/infoscreen_tags.py index 6fd158c..48de874 100644 --- a/infoscreen/templatetags/infoscreen_tags.py +++ b/infoscreen/templatetags/infoscreen_tags.py @@ -1,7 +1,5 @@ # imports from django import template -from django.conf import settings - # End: imports ----------------------------------------------------------------- register = template.Library() diff --git a/infoscreen/tests/tests.py b/infoscreen/tests/tests.py index 7ce503c..a39b155 100644 --- a/infoscreen/tests/tests.py +++ b/infoscreen/tests/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/infoscreen/urls.py b/infoscreen/urls.py index f9aa326..c3f38ce 100644 --- a/infoscreen/urls.py +++ b/infoscreen/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import path from django.views.generic.base import RedirectView from infoscreen import views @@ -10,4 +10,3 @@ path('screen//', views.ScreenView.as_view(), name='view_screen'), path('screens/', views.ScreenListView.as_view(), name='screen_list'), ] - diff --git a/infoscreen/utils/__init__.py b/infoscreen/utils/__init__.py index 2fbc278..acc94a2 100644 --- a/infoscreen/utils/__init__.py +++ b/infoscreen/utils/__init__.py @@ -1,16 +1,15 @@ -TEST1 = "https://youtu.be/16GVZ6s5XEw" -TEST2 = "https://www.youtube.com/watch?v=16GVZ6s5XEw&ab_channel=BigLozOfficial" -TEST3 = "https://youtu.be/16GVZ6s5XEw/?id=1" +TEST1 = 'https://youtu.be/16GVZ6s5XEw' +TEST2 = 'https://www.youtube.com/watch?v=16GVZ6s5XEw&ab_channel=BigLozOfficial' +TEST3 = 'https://youtu.be/16GVZ6s5XEw/?id=1' def parse_url(url): - if "https://youtu.be/" in url: - new = url.replace("https://youtu.be/", '') - new = new.split("/", 1) + if 'https://youtu.be/' in url: + new = url.replace('https://youtu.be/', '') + new = new.split('/', 1) return new[0] - elif "https://www.youtube.com/" in url: - new = url.split("=", 1) - new = new[1].split("&", 1) + if 'https://www.youtube.com/' in url: + new = url.split('=', 1) + new = new[1].split('&', 1) return new[0] - else: - return "oopsie woopsie, you made a fucky wucky" + return 'oopsie woopsie, you made a fucky wucky' diff --git a/root/constants.py b/root/constants.py index d172e39..2e1e696 100644 --- a/root/constants.py +++ b/root/constants.py @@ -1,4 +1,3 @@ - class Environment: """ Useful in eg. templates @@ -8,6 +7,6 @@ class Environment: DEV = 'development' HEROKU = 'heroku' PROD = 'production' - + ALL = [BASE, DEV, PROD, HEROKU] VALID = [DEV, PROD, HEROKU] diff --git a/root/models/__init__.py b/root/models/__init__.py index 8922107..3ebe28d 100644 --- a/root/models/__init__.py +++ b/root/models/__init__.py @@ -1 +1 @@ -from .base import CustomBaseAdmin, CustomBaseModel, CustomModelForm \ No newline at end of file +from .base import CustomBaseAdmin, CustomBaseModel, CustomModelForm diff --git a/root/models/base.py b/root/models/base.py index 961a726..42a4ff6 100644 --- a/root/models/base.py +++ b/root/models/base.py @@ -1,5 +1,4 @@ # imports -from datetime import time, date, datetime, timedelta from django import forms from django.db import models @@ -15,6 +14,7 @@ # https://github.com/django/django/blob/master/django/forms/models.py class CustomModelForm(forms.ModelForm): + # pylint: disable=keyword-arg-before-vararg def save(self, commit=True, *args, **kwargs): """ Override django ModelForm.save() to implement kwargs. Do not use super() in this class! @@ -25,6 +25,10 @@ def save(self, commit=True, *args, **kwargs): a save_m2m() method to the form which can be called after the instance is saved manually at a later time. Return the model instance. """ + # pylint: disable=invalid-string-quote + # pylint: disable=protected-access + # pylint: disable=consider-using-f-string + # pylint: disable=attribute-defined-outside-init if self.errors: raise ValueError("The %s could not be %s because the data didn't validate." % ( self.instance._meta.object_name, @@ -51,23 +55,24 @@ class CustomBaseAdmin(admin.ModelAdmin): # search_fields = [] def save_model(self, request, obj, form, change): + # pylint: disable=broad-except try: if not change: obj.creator = request.user obj.created = timezone.now() obj.last_editor = request.user obj.last_edited = timezone.now() - except Exception as e: + except Exception: pass return super().save_model(request, obj, form, change) class CustomBaseModel(models.Model): - last_editor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, editable=False, related_name="editor_%(class)s_set", verbose_name="Sist redigert av") - last_edited = models.DateTimeField(null=True, blank=True, editable=False, verbose_name="Sist redigert") - creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, editable=False, related_name="creator_%(class)s_set", verbose_name="Opprettet av") - created = models.DateTimeField(null=True, blank=True, editable=False, verbose_name="Opprettet") + last_editor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, editable=False, related_name='editor_%(class)s_set', verbose_name='Sist redigert av') + last_edited = models.DateTimeField(null=True, blank=True, editable=False, verbose_name='Sist redigert') + creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, editable=False, related_name='creator_%(class)s_set', verbose_name='Opprettet av') + created = models.DateTimeField(null=True, blank=True, editable=False, verbose_name='Opprettet') class Meta: abstract = True diff --git a/root/settings/base.py b/root/settings/base.py index d49254c..65efd1f 100644 --- a/root/settings/base.py +++ b/root/settings/base.py @@ -141,7 +141,7 @@ ################## LOGGING ################## -LOGFILENAME = f"{BASE_DIR}/info_screens.log" +LOGFILENAME = f'{BASE_DIR}/info_screens.log' LOGGING = { 'version': 1, diff --git a/root/settings/heroku.py b/root/settings/heroku.py index 34f4ac6..9f64051 100644 --- a/root/settings/heroku.py +++ b/root/settings/heroku.py @@ -1,14 +1,16 @@ -from .base import * +# pylint: disable=wildcard-import +# pylint: disable=unused-wildcard-import import os import django_heroku - +from .base import * ALLOWED_HOSTS = ['info-screens.herokuapp.com'] # Values are set in heroku dashboard SECRET_KEY = os.environ['SECRET_KEY'] -DEBUG = eval(os.environ['DEBUG']) +DEBUG = os.environ['DEBUG'] == 'True' +# pylint: disable=undefined-variable # Ensure correct ENV ENV = Environment.HEROKU @@ -16,8 +18,8 @@ STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' MIDDLEWARE += [ - 'whitenoise.middleware.WhiteNoiseMiddleware', # whitenoise, heroku + 'whitenoise.middleware.WhiteNoiseMiddleware', # whitenoise, heroku ] # activate django-heroku. -django_heroku.settings(locals()) \ No newline at end of file +django_heroku.settings(locals()) diff --git a/root/urls.py b/root/urls.py index d84c9d7..1075e49 100644 --- a/root/urls.py +++ b/root/urls.py @@ -20,17 +20,14 @@ from django.contrib import admin from django.conf.urls.static import static -from infoscreen import views as infoscreen_views - urlpatterns = [ path('admin/', admin.site.urls), - path('', include('infoscreen.urls')) + path('', include('infoscreen.urls')), ] urlpatterns += static(prefix=settings.STATIC_URL, document_root=settings.STATIC_ROOT) - if settings.DEBUG: urlpatterns += [ path('__debug__/', include(debug_toolbar.urls)), - ] \ No newline at end of file + ] diff --git a/run-pylint.sh b/run-pylint.sh new file mode 100644 index 0000000..e50f4f7 --- /dev/null +++ b/run-pylint.sh @@ -0,0 +1,5 @@ +#! /bin/bash +PROJECT_DIR=$(pwd) +PYTHONPATH="$PROJECT_DIR:$PYTHONPATH" +echo "PYTHONPATH=$PYTHONPATH" +PYTHONPATH=$PYTHONPATH pylint infoscreen root