From 44803538a486d7a1edd22d38ce4ae7ae44b11192 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 5 Aug 2019 14:16:25 +0700 Subject: [PATCH] Init --- .gitignore | 13 +++ apps/__init__.py | 0 apps/users/__init__.py | 1 + apps/users/admin.py | 70 +++++++++++++ apps/users/apps.py | 7 ++ apps/users/forms.py | 31 ++++++ apps/users/managers.py | 48 +++++++++ apps/users/migrations/0001_initial.py | 39 +++++++ apps/users/migrations/__init__.py | 0 apps/users/models.py | 103 ++++++++++++++++++ config/__init__.py | 0 config/settings/__init__.py | 0 config/settings/base.py | 144 ++++++++++++++++++++++++++ config/urls.py | 32 ++++++ config/wsgi.py | 16 +++ docker-compose.yml | 14 +++ manage.py | 21 ++++ requirements/base.txt | 2 + requirements/development.txt | 4 + 19 files changed, 545 insertions(+) create mode 100644 .gitignore create mode 100644 apps/__init__.py create mode 100755 apps/users/__init__.py create mode 100755 apps/users/admin.py create mode 100755 apps/users/apps.py create mode 100644 apps/users/forms.py create mode 100644 apps/users/managers.py create mode 100644 apps/users/migrations/0001_initial.py create mode 100644 apps/users/migrations/__init__.py create mode 100644 apps/users/models.py create mode 100644 config/__init__.py create mode 100644 config/settings/__init__.py create mode 100644 config/settings/base.py create mode 100644 config/urls.py create mode 100644 config/wsgi.py create mode 100644 docker-compose.yml create mode 100755 manage.py create mode 100644 requirements/base.txt create mode 100644 requirements/development.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed9d471 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +__pycache__ +__version__.py +.git +.virtualenv +.vscode +*.log +*.pyc +*.sublime-project +*.sublime-workspace +*.swp +*~ +local.py +media \ No newline at end of file diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/users/__init__.py b/apps/users/__init__.py new file mode 100755 index 0000000..115509b --- /dev/null +++ b/apps/users/__init__.py @@ -0,0 +1 @@ +default_app_config = 'apps.users.apps.UsersAppConfig' diff --git a/apps/users/admin.py b/apps/users/admin.py new file mode 100755 index 0000000..3330a70 --- /dev/null +++ b/apps/users/admin.py @@ -0,0 +1,70 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin +from django.utils.translation import ugettext_lazy as _ + +from .forms import CustomUserChangeForm, CustomUserCreationForm +from .models import User + +__all__ = ( + 'UserAdmin', +) + + +@admin.register(User) +class UserAdmin(DjangoUserAdmin): + """User admin. + + Admin class definitions for ``User`` model. + + """ + search_fields = ('first_name', 'last_name', 'email') + list_display = ( + 'id', + 'email', + 'date_joined', + 'last_login', + 'is_active', + 'is_staff', + 'is_superuser' + ) + list_display_links = ('email',) + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email', 'password1', 'password2'), + }), + ) + fieldsets = ( + (None, {'fields': ('email', 'password')}), + (_('Personal info'), { + 'fields': ( + 'first_name', + 'last_name', + ) + }), + (_('Permissions'), { + 'fields': ( + 'is_active', + 'is_staff', + 'is_superuser', + 'groups', + 'user_permissions' + ) + }), + (_('Important dates'), { + 'fields': ('last_login', 'date_joined') + }), + ) + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email', 'password1', 'password2'), + }), + ) + readonly_fields = DjangoUserAdmin.readonly_fields + ( + 'last_login', + 'date_joined', + ) + ordering = ('email',) + form = CustomUserChangeForm + add_form = CustomUserCreationForm diff --git a/apps/users/apps.py b/apps/users/apps.py new file mode 100755 index 0000000..87f7f1a --- /dev/null +++ b/apps/users/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class UsersAppConfig(AppConfig): + """Configuration for Users app.""" + name = 'apps.users' + verbose_name = 'Users' diff --git a/apps/users/forms.py b/apps/users/forms.py new file mode 100644 index 0000000..292f08c --- /dev/null +++ b/apps/users/forms.py @@ -0,0 +1,31 @@ +from django.contrib.auth.forms import UserChangeForm, UserCreationForm + +from .models import User + +__all__ = ('CustomUserChangeForm', 'CustomUserCreationForm') + + +class CustomUserChangeForm(UserChangeForm): + """Custom change form for ``User`` model. + + This class overrides ``UserChangeForm`` to provide different ``User`` + model in Meta. + + """ + + class Meta: + model = User + fields = '__all__' + + +class CustomUserCreationForm(UserCreationForm): + """Custom creation form for ``User`` model. + + This class overrides ``UserCreationForm`` to provide different ``User`` + model in Meta and also replaces ``username`` field with ``email`` field. + + """ + + class Meta: + model = User + fields = ('email',) diff --git a/apps/users/managers.py b/apps/users/managers.py new file mode 100644 index 0000000..b82772f --- /dev/null +++ b/apps/users/managers.py @@ -0,0 +1,48 @@ +from django.contrib.auth.models import BaseUserManager +from django.utils.translation import ugettext_lazy as _ + +__all__ = ( + 'UserManager', +) + + +class UserManager(BaseUserManager): + """Custom user manager. + + The custom user manager needs instead base manager because we use + ``email`` instead ``username`` for authentication. + + """ + + def create_user(self, email, password=None): + """Create user. + + Overridden base user manager method, customized for auth with email + instead username. + + """ + + if not email: + raise ValueError(_("Users must have an email address!")) + + user = self.model(email=self.normalize_email(email),) + + user.set_password(password) + user.save(using=self._db) + + return user + + def create_superuser(self, email, password): + """Create superuser. + + Overridden base user manager method, customized for auth with email + instead username. + + """ + user = self.create_user(email, password=password) + + user.is_superuser = True + user.is_staff = True + user.save(using=self._db) + + return user diff --git a/apps/users/migrations/0001_initial.py b/apps/users/migrations/0001_initial.py new file mode 100644 index 0000000..b7533d9 --- /dev/null +++ b/apps/users/migrations/0001_initial.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.3 on 2019-07-31 15:13 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0011_update_proxy_permissions'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.AutoField(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')), + ('first_name', models.CharField(max_length=255, verbose_name='First name')), + ('last_name', models.CharField(max_length=255, verbose_name='Last name')), + ('email', models.EmailField(max_length=255, unique=True, verbose_name='Email')), + ('is_active', models.BooleanField(default=True, verbose_name='Is active')), + ('is_staff', models.BooleanField(default=False, help_text='The user will have access to admin interface.', verbose_name='Is staff')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date joined')), + ('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={ + 'verbose_name': 'User', + 'verbose_name_plural': 'Users', + 'db_table': 'users', + 'ordering': ('email',), + }, + ), + ] diff --git a/apps/users/migrations/__init__.py b/apps/users/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/users/models.py b/apps/users/models.py new file mode 100644 index 0000000..1a84e6e --- /dev/null +++ b/apps/users/models.py @@ -0,0 +1,103 @@ +from urllib.parse import urljoin + +from django.urls import reverse +from django.conf import settings +from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin +from django.contrib.postgres.fields import JSONField +from django.db import models +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ + +from .managers import UserManager + +__all__ = ( + 'User', +) + + +class User(AbstractBaseUser, PermissionsMixin): + """Custom user model. + + Attributes: + first_name (str): First name. + last_name (str): Last, family name. + email (str): E-mail, uses for authentication. + is_active (bool): Can user log in to the system. + is_staff (bool): Can user access to admin interface. + date_joined (datetime): Date when the account was created. + + Nested attributes: + is_superuser (bool): The user can super access to admin UI. + groups(Manager): The groups this user belongs to. + user_permissions(Manager): Specified permissions for this user. + last_login (datetime): Last date when user login to the system. + + """ + EMAIL_FIELD = 'email' + USERNAME_FIELD = 'email' + + first_name = models.CharField( + max_length=255, + verbose_name=_("First name") + ) + last_name = models.CharField( + max_length=255, + verbose_name=_("Last name") + ) + email = models.EmailField( + max_length=255, + unique=True, + verbose_name=_("Email") + ) + is_active = models.BooleanField( + default=True, + verbose_name=_("Is active"), + ) + is_staff = models.BooleanField( + default=False, + verbose_name=_("Is staff"), + help_text=_("The user will have access to admin interface."), + ) + date_joined = models.DateTimeField( + default=timezone.now, + verbose_name=_("Date joined"), + ) + + objects = UserManager() + + class Meta: + db_table = 'users' + ordering = ('email',) + verbose_name = _("User") + verbose_name_plural = _("Users") + + def __str__(self): + return self.email + + def get_full_name(self): + full_name = '{first_name} {last_name}'.format( + first_name=self.last_name, + last_name=self.first_name, + ) + + return full_name.strip() + + def get_short_name(self): + return self.first_name + + def get_admin_change_url(self) -> str: + """Get admin change URL. + + Build full url (host + path) to standard Django admin page for + object like: + + https://api.sitename.com/admin/users/user/234/ + + """ + + assert self.id, "Instance must have an ID" + + return urljoin( + settings.DJANGO_SITE_BASE_HOST, + reverse('admin:users_user_change', args=(self.id,)), + ) diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/settings/__init__.py b/config/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/settings/base.py b/config/settings/base.py new file mode 100644 index 0000000..335fb5e --- /dev/null +++ b/config/settings/base.py @@ -0,0 +1,144 @@ +""" +Django settings for {{ project_name }} project. + +Generated by 'django-admin startproject' using Django 2.2.3. + +For more information on this file, see +https://docs.djangoproject.com/en/2.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.2/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '=-3-bm8a-2qz6b78%vfa_9!rfp7yvlm3f6w-rof34yx-==d29v' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'apps.users', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'config.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'config.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/2.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.contrib.gis.db.backends.postgis', + 'NAME': 'dev', + 'USER': 'postgres', + 'PASSWORD': 'postgres', + 'HOST': 'postgres', + 'PORT': '5432', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/2.2/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' + ), + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/2.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.2/howto/static-files/ + +STATIC_URL = '/static/' +MEDIA_URL = '/media/' + +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +STATIC_ROOT = os.path.join(BASE_DIR, 'static') + +AUTH_USER_MODEL = 'users.User' diff --git a/config/urls.py b/config/urls.py new file mode 100644 index 0000000..e72780d --- /dev/null +++ b/config/urls.py @@ -0,0 +1,32 @@ +"""{{ project_name }} URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/2.2/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.conf import settings +from django.contrib import admin +from django.urls import path +from django.conf.urls.static import static + +urlpatterns = [ + path('admin/', admin.site.urls), +] + +# for serving uploaded files on dev environment with django +if settings.DEBUG: + urlpatterns += static( + settings.MEDIA_URL, document_root=settings.MEDIA_ROOT + ) + urlpatterns += static( + settings.STATIC_URL, document_root=settings.STATIC_ROOT + ) diff --git a/config/wsgi.py b/config/wsgi.py new file mode 100644 index 0000000..a3d216e --- /dev/null +++ b/config/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for {{ project_name }} 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/2.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + +application = get_wsgi_application() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..181ce90 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.7' + +volumes: + ipython_history: {} + +services: + postgres: + image: mdillon/postgis:10 + ports: + - "5432:5432" + environment: + - POSTGRES_DB=dev + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..72a0162 --- /dev/null +++ b/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local') + 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/requirements/base.txt b/requirements/base.txt new file mode 100644 index 0000000..39dc101 --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,2 @@ +Django==2.2.3 +psycopg2-binary diff --git a/requirements/development.txt b/requirements/development.txt new file mode 100644 index 0000000..2576dcf --- /dev/null +++ b/requirements/development.txt @@ -0,0 +1,4 @@ +-r base.txt + +ipython +ipdb