diff --git a/backend/.coveragerc b/backend/.coveragerc new file mode 100644 index 0000000..0a14deb --- /dev/null +++ b/backend/.coveragerc @@ -0,0 +1,7 @@ +[run] +omit = */migrations/* + +[paths] +source = + apps/ + config/ diff --git a/backend/.pre-commit-config.yaml b/backend/.pre-commit-config.yaml index 5075b52..de32a8a 100644 --- a/backend/.pre-commit-config.yaml +++ b/backend/.pre-commit-config.yaml @@ -5,9 +5,16 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - - id: check-yaml - - id: end-of-file-fixer - id: trailing-whitespace + # - id: end-of-file-fixer + - id: check-yaml + - id: debug-statements + + - repo: https://github.com/psf/black + rev: 23.7.0 + hooks: + - id: black + files: ^backend/ - repo: https://github.com/pre-commit/mirrors-mypy rev: 'v1.5.1' hooks: diff --git a/backend/Dockerfile b/backend/Dockerfile index ae7e0e0..d611fc9 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -13,4 +13,4 @@ EXPOSE 8000 RUN ["chmod", "+x", "./entrypoint.sh"] -CMD "./entrypoint.sh" \ No newline at end of file +CMD "./entrypoint.sh" diff --git a/backend/authentication/tests.py b/backend/authentication/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/backend/authentication/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/backend/authentication/views.py b/backend/authentication/views.py index 9c58df9..d48ebb2 100644 --- a/backend/authentication/views.py +++ b/backend/authentication/views.py @@ -1,10 +1,4 @@ -import http - -import jwt -from rest_framework.response import Response -from rest_framework.views import APIView from rest_framework_simplejwt.views import TokenObtainPairView -from django.contrib.auth import authenticate, login from authentication.serializers import TokenSerializer diff --git a/backend/core/models.py b/backend/core/models.py index 5fa2dfd..763ce3e 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -1,15 +1,15 @@ -from django.db import models from uuid import uuid4 +from django.db import models + class BaseModel(models.Model): - uuid = models.UUIDField( - primary_key=True, - default=uuid4, - editable=False) - created_date = models.DateTimeField(auto_now_add=True) - updated_date = models.DateTimeField(auto_now=True) - is_deleted = models.BooleanField(default=False) + uuid: models.UUIDField = models.UUIDField( + primary_key=True, default=uuid4, editable=False + ) + created_date: models.DateTimeField = models.DateTimeField(auto_now_add=True) + updated_date: models.DateTimeField = models.DateTimeField(auto_now=True) + is_deleted: models.BooleanField = models.BooleanField(default=False) class Meta: - abstract = True \ No newline at end of file + abstract = True diff --git a/backend/core/settings.py b/backend/core/settings.py index 17496c8..1e88bf0 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -79,22 +79,26 @@ }, ] +PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",) + WSGI_APPLICATION = "core.wsgi.application" # Database # https://docs.djangoproject.com/en/4.1/ref/settings/#databases DATABASES = { + # "default": { + # "ENGINE": "django.db.backends.postgresql_psycopg2", + # "NAME": os.environ.get("DB_NAME", "photo-library-ns"), + # "USER": os.environ.get("DB_USER", "photo-library-ns"), + # "PASSWORD": os.environ.get("DB_PASSWORD", "photo-library-ns#01"), + # "HOST": os.environ.get("DB_HOST", "db"), + # "PORT": os.environ.get("DB_PORT", "5432"), + # }, "default": { - "ENGINE": "django.db.backends.postgresql_psycopg2", - "NAME": os.environ.get("DB_NAME", ""), - "USER": os.environ.get("DB_USER", ""), - "PASSWORD": os.environ.get("DB_PASSWORD", ""), - "HOST": os.environ.get("DB_HOST", ""), - "PORT": os.environ.get("DB_PORT", "5432"), + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", }, - - "default": {"ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3"} } # Password validation @@ -129,8 +133,10 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.1/howto/static-files/ -STATIC_URL = "/staticfiles/" -STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") +STATIC_URL = "/static/" +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "static"), +] MEDIA_URL = "/media/" @@ -142,7 +148,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" REST_FRAMEWORK = { - "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "PAGE_SIZE": 9999, "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", diff --git a/backend/core/settings_test.py b/backend/core/settings_test.py new file mode 100644 index 0000000..ca40e54 --- /dev/null +++ b/backend/core/settings_test.py @@ -0,0 +1,17 @@ +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + } +} + +MIDDLEWARE_CLASSES = [ + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", +] + +DEBUG = False + +EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index b1b8593..8a6b676 100644 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -1,13 +1,14 @@ #!/bin/bash -# Collect static files + +echo "Migrations" +python3 manage.py migrate --no-input + echo "Collect static files" python3 manage.py collectstatic --no-input -# Start server echo "Starting server" # python3 manage.py runserver 0.0.0.0:8000 gunicorn --config gunicorn-cfg.py core.wsgi exec "$@" - diff --git a/backend/enviroments.env b/backend/enviroments.env index 5929f52..e482f49 100644 --- a/backend/enviroments.env +++ b/backend/enviroments.env @@ -8,4 +8,3 @@ DB_USER=s208 DB_PASSWORD=C6A2_ff8d9d DB_HOST=psql01.mikr.us DB_PORT=5432 - diff --git a/backend/history/tests.py b/backend/history/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/backend/history/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/backend/mypy.ini b/backend/mypy.ini index af3d98a..30bc736 100644 --- a/backend/mypy.ini +++ b/backend/mypy.ini @@ -1,10 +1,7 @@ [mypy] -# results. python_version = 3.11 -plugins = - mypy_django_plugin.main, - mypy_drf_plugin.main +plugins = mypy_django_plugin.main, mypy_drf_plugin.main ignore_missing_imports = True warn_unused_ignores = True @@ -15,12 +12,3 @@ exclude = venv [mypy.plugins.django-stubs] django_settings_module = "core.settings" namespace_packages = True - -[mypy-*.migrations.*] -ignore_errors = True - -[mypy-tests.*] -disallow_untyped_defs = False - -[mypy_django_plugin] -ignore_missing_model_attributes = True diff --git a/backend/photos/admin.py b/backend/photos/admin.py index 08a7e6f..09e6f62 100644 --- a/backend/photos/admin.py +++ b/backend/photos/admin.py @@ -3,15 +3,13 @@ from photos.models import Photo, PhotoPositions +@admin.register(Photo) class PhotoAdmin(admin.ModelAdmin): date_hierarchy = "created_date" list_display = ["uuid", "status", "photo", "created_date", "column_id", "order"] +@admin.register(PhotoPositions) class PhotoPositionsAdmin(admin.ModelAdmin): date_hierarchy = "created_date" list_display = ["uuid", "created_date"] - - -admin.site.register(Photo, PhotoAdmin) -admin.site.register(PhotoPositions, PhotoPositionsAdmin) diff --git a/backend/photos/models.py b/backend/photos/models.py index 612cfec..3c38680 100644 --- a/backend/photos/models.py +++ b/backend/photos/models.py @@ -13,7 +13,7 @@ def upload_to(instance, filename): class PhotoPositions(BaseModel): - columns = models.JSONField(null=True) + columns: models.JSONField = models.JSONField(null=True) class Photo(BaseModel): @@ -22,15 +22,17 @@ class Photo(BaseModel): (POSITIONED, "positioned at last configuration"), (UNPOSITIONED, "not positioned"), ] - photo = models.ImageField( + photo: models.ImageField = models.ImageField( upload_to=upload_to, max_length=100, verbose_name="photos", validators=[FileExtensionValidator(["jpg", "jpeg"])], ) - column_id = models.IntegerField(default=1) - order = models.IntegerField(default=0) - status = models.CharField(choices=STATUS_PHOTO, max_length=20, default=NEW) + column_id: models.IntegerField = models.IntegerField(default=1) + order: models.IntegerField = models.IntegerField(default=0) + status: models.CharField = models.CharField( + choices=STATUS_PHOTO, max_length=20, default=NEW + ) def __str__(self): return f"{self.uuid} - {self.photo}" diff --git a/backend/photos/tests.py b/backend/photos/tests.py deleted file mode 100644 index a39b155..0000000 --- a/backend/photos/tests.py +++ /dev/null @@ -1 +0,0 @@ -# Create your tests here. diff --git a/backend/photos/tests/__init__.py b/backend/photos/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/photos/tests/factory.py b/backend/photos/tests/factory.py new file mode 100644 index 0000000..6122cd9 --- /dev/null +++ b/backend/photos/tests/factory.py @@ -0,0 +1,40 @@ +import json + +from factory import LazyFunction +from factory.django import DjangoModelFactory +from faker import Faker +from faker.providers import file, misc, date_time + +from photos.models import Photo, PhotoPositions + +faker = Faker() +faker.add_provider(file) +faker.add_provider(misc) +faker.add_provider(date_time) + +STATUS = [x[0] for x in Photo.STATUS_PHOTO] + + +class PhotoFactory(DjangoModelFactory): + uuid = LazyFunction(lambda: faker.uuid4()) + photo = LazyFunction(lambda: faker.file_path(depth=3, extension="jpg")) + column_id = LazyFunction(lambda: faker.random_int(min=0, max=3)) + order = LazyFunction(lambda: faker.random_int()) + status = LazyFunction(lambda: faker.random_choices(elements=STATUS)) + created_date = LazyFunction(lambda: faker.date_time()) + + class Meta: + model = Photo + + +class PhotoPositionsFactory(DjangoModelFactory): + columns = LazyFunction( + lambda: { + "1": json.dumps([PhotoFactory.build() for _ in range(faker.random_int())]), + "2": json.dumps([PhotoFactory.build() for _ in range(faker.random_int())]), + "3": json.dumps([PhotoFactory.build() for _ in range(faker.random_int())]), + } + ) + + class Meta: + model = PhotoPositions diff --git a/backend/photos/tests/test_models.py b/backend/photos/tests/test_models.py new file mode 100644 index 0000000..ea54070 --- /dev/null +++ b/backend/photos/tests/test_models.py @@ -0,0 +1,23 @@ +import datetime + +import pytest +from django.db.models.fields.files import ImageFieldFile + +from photos.models import Photo +from photos.tests.factory import PhotoFactory + + +@pytest.mark.django_db +class TestPhoto: + @pytest.fixture + def photo(self): + return PhotoFactory.create() + + def test_instance_photo(self, photo): + assert isinstance(photo, Photo) + assert isinstance(photo.uuid, str) + assert isinstance(photo.photo, ImageFieldFile) + assert isinstance(photo.column_id, int) + assert isinstance(photo.status, list) + assert isinstance(photo.order, int) + assert isinstance(photo.created_date, datetime.datetime) diff --git a/backend/photos/tests/test_services.py b/backend/photos/tests/test_services.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/pytest.ini b/backend/pytest.ini new file mode 100644 index 0000000..9f809d1 --- /dev/null +++ b/backend/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +DJANGO_SETTINGS_MODULE = core.settings + +python_files = tests.py test_*.py *_tests.py +addopts = -s -q --disable-warnings --doctest-modules +norecursedirs = .git .cache tmp* diff --git a/backend/requirements.txt b/backend/requirements.txt index 444b140..3d73232 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,15 +1,42 @@ asgiref==3.6.0 +certifi==2023.7.22 +charset-normalizer==3.2.0 +coverage==7.3.0 Django==4.1.7 django-cors-headers==3.14.0 django-environ==0.10.0 django-rest-framework==0.1.0 +django-stubs==4.2.3 +django-stubs-ext==4.2.2 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 +djangorestframework-stubs==3.14.2 +Faker==12.0.1 gunicorn==21.2.0 +idna==3.4 +iniconfig==2.0.0 +mixer==7.2.2 +mypy==1.5.1 +mypy-extensions==1.0.0 packaging==23.1 Pillow==10.0.0 +pluggy==1.2.0 psycopg2-binary==2.9.6 PyJWT==2.7.0 +pytest==7.4.0 +pytest-cov==4.1.0 +pytest-django==4.5.2 +pytest-sugar==0.9.7 +python-dateutil==2.8.2 pytz==2023.3 +requests==2.31.0 +six==1.16.0 sqlparse==0.4.3 +termcolor==2.3.0 +types-pytz==2023.3.0.1 +types-PyYAML==6.0.12.11 +types-requests==2.31.0.2 +types-urllib3==1.26.25.14 +typing_extensions==4.7.1 +urllib3==2.0.4 whitenoise==6.5.0 diff --git a/docker-compose.yml b/docker-compose.yml index 3c09124..b75004d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,8 +13,8 @@ services: - static:/code/static/ ports: - "8080:8000" - env_file: - - environments.env + depends_on: + - db frontend: build: @@ -23,8 +23,18 @@ services: - "88:80" depends_on: - backend - env_file: - - environments.env + + db: + image: postgres:13-alpine + environment: + - POSTGRES_DB=photo-library-ns + - POSTGRES_USER=photo-library-ns + - POSTGRES_PASSWORD=photo-library-ns#01 + ports: + - "5432:5432" + volumes: + - postgres-db:/var/lib/postgresql/data + app: image: 'jc21/nginx-proxy-manager:latest' container_name: nginxproxymanager @@ -43,4 +53,4 @@ services: volumes: media: static: - + postgres-db: