diff --git a/.gitignore b/.gitignore index b6e4761..ed8f6af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +media/ +static/ +.idea/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/app/conf/__init__.py b/app/conf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/conf/asgi.py b/app/conf/asgi.py new file mode 100644 index 0000000..444f807 --- /dev/null +++ b/app/conf/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for conf project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "conf.settings") + +application = get_asgi_application() diff --git a/app/conf/settings/__init__.py b/app/conf/settings/__init__.py new file mode 100644 index 0000000..49202e0 --- /dev/null +++ b/app/conf/settings/__init__.py @@ -0,0 +1 @@ +from .local import * \ No newline at end of file diff --git a/app/conf/settings/base.py b/app/conf/settings/base.py new file mode 100644 index 0000000..b813523 --- /dev/null +++ b/app/conf/settings/base.py @@ -0,0 +1,221 @@ +from pathlib import Path + +import environ + +ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent +env = environ.Env() + +READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=True) +if READ_DOT_ENV_FILE: + # OS environment variables take precedence over variables from .env + env.read_env(str(ROOT_DIR / ".env")) + +# GENERAL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#debug +DEBUG = env.bool("DJANGO_DEBUG", False) +# Local time zone. Choices are +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# though not all of them may be available with every OS. +# In Windows, this must be set to your system time zone. +TIME_ZONE = "Europe/Moscow" +# https://docs.djangoproject.com/en/dev/ref/settings/#language-code +LANGUAGE_CODE = "en-us" +# https://docs.djangoproject.com/en/dev/ref/settings/#site-id +SITE_ID = 1 +# https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n +USE_I18N = True +# https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n +USE_L10N = True +# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz +USE_TZ = True +# https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths +LOCALE_PATHS = [str(ROOT_DIR / "locale")] + +# DATABASES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#databases +DATABASES = {"default": env.db("DATABASE_URL")} +DATABASES["default"]["ATOMIC_REQUESTS"] = True +# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-DEFAULT_AUTO_FIELD +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# URLS +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf +ROOT_URLCONF = "conf.urls" +# https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application +WSGI_APPLICATION = "conf.wsgi.application" + +# APPS +# ------------------------------------------------------------------------------ +DJANGO_APPS = [ + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.sites", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.humanize", + "django.contrib.admin", + "django.forms", +] +THIRD_PARTY_APPS = [ + "rest_framework", + "corsheaders", +] + +LOCAL_APPS = ["users"] +# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps +INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS + +# AUTHENTICATION +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends +AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", +] +# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model +AUTH_USER_MODEL = "users.User" + +# PASSWORDS +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" + }, + {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, +] + +# MIDDLEWARE +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#middleware +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "corsheaders.middleware.CorsMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.common.BrokenLinkEmailsMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +# TEMPLATES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#templates +TEMPLATES = [ + { + # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND + "BACKEND": "django.template.backends.django.DjangoTemplates", + # https://docs.djangoproject.com/en/dev/ref/settings/#dirs + "DIRS": [str(ROOT_DIR / "templates")], + # https://docs.djangoproject.com/en/dev/ref/settings/#app-dirs + "APP_DIRS": True, + "OPTIONS": { + # https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + ], + }, + } +] + +# https://docs.djangoproject.com/en/dev/ref/settings/#form-renderer +FORM_RENDERER = "django.forms.renderers.TemplatesSetting" + +# FIXTURES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs +FIXTURE_DIRS = (str(ROOT_DIR / "fixtures"),) + +# SECURITY +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly +SESSION_COOKIE_HTTPONLY = True +# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly +CSRF_COOKIE_HTTPONLY = True +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter +SECURE_BROWSER_XSS_FILTER = True +# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options +X_FRAME_OPTIONS = "DENY" + +# ADMIN +# ------------------------------------------------------------------------------ +# Django Admin URL. +ADMIN_URL = "admin/" +# https://docs.djangoproject.com/en/dev/ref/settings/#admins +ADMINS = [("""sanspie""", "alexandr.d.karpov@gmail.com")] +# https://docs.djangoproject.com/en/dev/ref/settings/#managers +MANAGERS = ADMINS + +# LOGGING +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#logging +# See https://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s " + "%(process)d %(thread)d %(message)s" + } + }, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + } + }, + "root": {"level": "INFO", "handlers": ["console"]}, +} + +# Celery +# ------------------------------------------------------------------------------ +if USE_TZ: + # https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-timezone + CELERY_TIMEZONE = TIME_ZONE +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-broker_url +CELERY_BROKER_URL = env("CELERY_BROKER_URL") +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-result_backend +CELERY_RESULT_BACKEND = CELERY_BROKER_URL +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-accept_content +CELERY_ACCEPT_CONTENT = ["json"] +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-task_serializer +CELERY_TASK_SERIALIZER = "json" +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-result_serializer +CELERY_RESULT_SERIALIZER = "json" +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-time-limit +CELERY_TASK_TIME_LIMIT = 5 * 60 +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-soft-time-limit +CELERY_TASK_SOFT_TIME_LIMIT = 60 +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#beat-scheduler +CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" + + +# django-rest-framework +# ------------------------------------------------------------------------------- +REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_simplejwt.authentication.JWTAuthentication", + ), + "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), +} + +# django-cors-headers +CORS_ALLOW_ALL_ORIGINS = True diff --git a/app/conf/settings/local.py b/app/conf/settings/local.py new file mode 100644 index 0000000..a5a6c14 --- /dev/null +++ b/app/conf/settings/local.py @@ -0,0 +1,61 @@ +from datetime import timedelta + +from .base import * +from .base import env + +# GENERAL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#debug +DEBUG = True +# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +SECRET_KEY = env( + "DJANGO_SECRET_KEY", + default="7IFkV9gOD8gP3RbPfBuFmcUE4rPVvlnTlVScHr8OBCmHQrA1OIl2la2TJqKIBkTu", +) +# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts +ALLOWED_HOSTS = ["*"] + +# CACHES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#caches +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "", + } +} + +# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips +INTERNAL_IPS = ["127.0.0.1"] + +# DRF +# ------------------------------------------------------------------------------ +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=10000), +} + +REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_simplejwt.authentication.JWTAuthentication", + ), + "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.AllowAny",), +} + +# STATIC +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#static-root +STATIC_ROOT = str(ROOT_DIR / "static") +# https://docs.djangoproject.com/en/dev/ref/settings/#static-url +STATIC_URL = "/static/" +# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders +STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", +] + +# MEDIA +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#media-root +MEDIA_ROOT = str(ROOT_DIR / "media") +# https://docs.djangoproject.com/en/dev/ref/settings/#media-url +MEDIA_URL = "/media/" diff --git a/app/conf/urls.py b/app/conf/urls.py new file mode 100644 index 0000000..f7faa20 --- /dev/null +++ b/app/conf/urls.py @@ -0,0 +1,21 @@ +"""conf URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path("admin/", admin.site.urls), +] diff --git a/app/conf/wsgi.py b/app/conf/wsgi.py new file mode 100644 index 0000000..720a139 --- /dev/null +++ b/app/conf/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for conf project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "conf.settings") + +application = get_wsgi_application() diff --git a/app/manage.py b/app/manage.py new file mode 100755 index 0000000..39cfb26 --- /dev/null +++ b/app/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "conf.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/app/requirements/base.txt b/app/requirements/base.txt new file mode 100644 index 0000000..ece643a --- /dev/null +++ b/app/requirements/base.txt @@ -0,0 +1,9 @@ +celery==5.2.7 +djangorestframework==3.14.0 +djangorestframework-simplejwt==5.2.1 +Django==4.1.2 +django-cors-headers==3.13.0 +django-environ==0.9.0 + +redis==4.3.4 +psycopg2-binary==2.9.4 \ No newline at end of file diff --git a/app/users/__init__.py b/app/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/users/admin.py b/app/users/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/app/users/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/app/users/apps.py b/app/users/apps.py new file mode 100644 index 0000000..88f7b17 --- /dev/null +++ b/app/users/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "users" diff --git a/app/users/migrations/0001_initial.py b/app/users/migrations/0001_initial.py new file mode 100644 index 0000000..1560418 --- /dev/null +++ b/app/users/migrations/0001_initial.py @@ -0,0 +1,119 @@ +# Generated by Django 4.1.2 on 2022-10-07 19:06 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ] + + operations = [ + migrations.CreateModel( + name="User", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ("about", models.TextField(blank=True)), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", + ), + ), + ], + options={ + "ordering": ["-id"], + }, + managers=[ + ("objects", django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/app/users/migrations/__init__.py b/app/users/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/users/models.py b/app/users/models.py new file mode 100644 index 0000000..a36e606 --- /dev/null +++ b/app/users/models.py @@ -0,0 +1,20 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models + + +class User(AbstractUser): + """Base user model, to store all user info""" + + first_name = None + last_name = None + + # image = models.ImageField(upload_to=user_file_upload_mixin, blank=True) + # image_cropped = models.ImageField(upload_to="cropped/", blank=True) + + about = models.TextField(blank=True) + + def __str__(self): + return self.username + + class Meta: + ordering = ["-id"] diff --git a/app/users/tests.py b/app/users/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/app/users/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/users/views.py b/app/users/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/app/users/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.