├── myproject ├── __init__.py ├── core │ ├── __init__.py │ ├── tests.py │ ├── apps.py │ ├── urls.py │ └── views.py ├── crm │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── views.py │ ├── apps.py │ ├── admin.py │ ├── models.py │ └── tests.py ├── accounts │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── views.py │ ├── apps.py │ ├── urls.py │ ├── managers.py │ ├── admin.py │ ├── tests.py │ └── models.py ├── urls.py ├── asgi.py ├── wsgi.py └── settings.py ├── img ├── models.png ├── youtube.png └── diagram0.png ├── requirements.txt ├── manage.py ├── contrib └── env_gen.py ├── README.md ├── .gitignore └── passo-a-passo.md /myproject/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /myproject/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /myproject/crm/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /myproject/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /myproject/crm/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /myproject/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/models.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-custom-login-email/main/img/models.png -------------------------------------------------------------------------------- /img/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-custom-login-email/main/img/youtube.png -------------------------------------------------------------------------------- /myproject/core/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /myproject/crm/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /img/diagram0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-custom-login-email/main/img/diagram0.png -------------------------------------------------------------------------------- /myproject/crm/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CrmConfig(AppConfig): 5 | name = 'crm' 6 | -------------------------------------------------------------------------------- /myproject/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = 'core' 6 | -------------------------------------------------------------------------------- /myproject/accounts/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Read https://github.com/rg3915/django-auth-tutorial 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==3.1.* 2 | dj-database-url==0.5.* 3 | django-extensions==3.1.* 4 | isort==5.8.* 5 | python-decouple==3.* 6 | -------------------------------------------------------------------------------- /myproject/accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | name = 'accounts' 6 | -------------------------------------------------------------------------------- /myproject/core/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from myproject.core import views as v 4 | 5 | app_name = 'core' 6 | 7 | 8 | urlpatterns = [ 9 | path('', v.index, name='index'), 10 | ] 11 | -------------------------------------------------------------------------------- /myproject/crm/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Phone 4 | 5 | 6 | @admin.register(Phone) 7 | class PhoneAdmin(admin.ModelAdmin): 8 | list_display = ('__str__', 'phone') 9 | search_fields = ('user__email',) 10 | -------------------------------------------------------------------------------- /myproject/core/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.http import HttpResponse 3 | from django.shortcuts import render 4 | 5 | 6 | # @login_required 7 | def index(request): 8 | return HttpResponse('

Django

Página simples.

') 9 | -------------------------------------------------------------------------------- /myproject/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import include, path 3 | 4 | urlpatterns = [ 5 | path('', include('myproject.core.urls', namespace='core')), 6 | path('accounts/', include('myproject.accounts.urls')), # without namespace 7 | path('admin/', admin.site.urls), 8 | ] 9 | -------------------------------------------------------------------------------- /myproject/accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.views import LoginView, LogoutView 2 | from django.urls import path 3 | 4 | # Read https://github.com/rg3915/django-auth-tutorial 5 | 6 | urlpatterns = [ 7 | path('login/', LoginView.as_view(), name='login'), 8 | path('logout/', LogoutView.as_view(), name='logout'), 9 | ] 10 | -------------------------------------------------------------------------------- /myproject/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for myproject project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /myproject/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for myproject project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /myproject/crm/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | 4 | 5 | class Phone(models.Model): 6 | phone = models.CharField(max_length=13) 7 | user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) # noqa E501 8 | 9 | class Meta: 10 | ordering = ('-pk',) 11 | verbose_name = 'telefone' 12 | verbose_name_plural = 'telefones' 13 | 14 | def __str__(self): 15 | return self.user.email 16 | -------------------------------------------------------------------------------- /myproject/crm/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from myproject.accounts.models import User 4 | from myproject.crm.models import Phone 5 | 6 | 7 | class TestUser(TestCase): 8 | def setUp(self): 9 | self.user = User.objects.create_user( 10 | email='admin@email.com', 11 | password='demodemo', 12 | first_name='Admin', 13 | last_name='Admin', 14 | ) 15 | self.phone = Phone.objects.create( 16 | phone='9876-54321', 17 | user=self.user 18 | ) 19 | 20 | def test_phone_exists(self): 21 | self.assertTrue(self.phone) 22 | 23 | def test_str(self): 24 | self.assertEqual(self.user.email, 'admin@email.com') 25 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /myproject/crm/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.8 on 2021-04-14 07:23 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Phone', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('phone', models.CharField(max_length=13)), 22 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 23 | ], 24 | options={ 25 | 'verbose_name': 'telefone', 26 | 'verbose_name_plural': 'telefones', 27 | 'ordering': ('-pk',), 28 | }, 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /contrib/env_gen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python SECRET_KEY generator. 3 | """ 4 | import random 5 | 6 | chars = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!?@#$%^&*()" 7 | size = 50 8 | secret_key = "".join(random.sample(chars, size)) 9 | 10 | chars = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!?@#$%_" 11 | size = 20 12 | password = "".join(random.sample(chars, size)) 13 | 14 | CONFIG_STRING = """ 15 | DEBUG=True 16 | SECRET_KEY=%s 17 | ALLOWED_HOSTS=127.0.0.1,.localhost,0.0.0.0 18 | 19 | #DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/NAME 20 | #POSTGRES_DB= 21 | #POSTGRES_USER= 22 | #POSTGRES_PASSWORD=%s 23 | #DB_HOST=localhost 24 | 25 | #DEFAULT_FROM_EMAIL= 26 | #EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend 27 | #EMAIL_HOST=localhost 28 | #EMAIL_PORT= 29 | #EMAIL_HOST_USER= 30 | #EMAIL_HOST_PASSWORD= 31 | #EMAIL_USE_TLS=True 32 | """.strip() % (secret_key, password) 33 | 34 | # Writing our configuration file to '.env' 35 | with open('.env', 'w') as configfile: 36 | configfile.write(CONFIG_STRING) 37 | 38 | print('Success!') 39 | print('Type: cat .env') 40 | -------------------------------------------------------------------------------- /myproject/accounts/managers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.base_user import BaseUserManager 2 | 3 | 4 | class UserManager(BaseUserManager): 5 | use_in_migrations = True 6 | 7 | def _create_user(self, email, password, **extra_fields): 8 | """ 9 | Creates and saves a User with the given email and password. 10 | """ 11 | if not email: 12 | raise ValueError('The given email must be set') 13 | email = self.normalize_email(email) 14 | user = self.model(email=email, **extra_fields) 15 | user.set_password(password) 16 | user.save(using=self._db) 17 | return user 18 | 19 | def create_user(self, email, password=None, **extra_fields): 20 | extra_fields.setdefault('is_superuser', False) 21 | return self._create_user(email, password, **extra_fields) 22 | 23 | def create_superuser(self, email, password, **extra_fields): 24 | extra_fields.setdefault('is_admin', True) 25 | extra_fields.setdefault('is_superuser', True) 26 | 27 | if extra_fields.get('is_admin') is not True: 28 | raise ValueError('Superuser must have is_admin=True.') 29 | if extra_fields.get('is_superuser') is not True: 30 | raise ValueError('Superuser must have is_superuser=True.') 31 | 32 | return self._create_user(email, password, **extra_fields) 33 | -------------------------------------------------------------------------------- /myproject/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin as BaseUserAdmin 3 | from django.utils.translation import gettext_lazy as _ 4 | 5 | from .models import User 6 | 7 | 8 | class UserAdmin(BaseUserAdmin): 9 | # The fields to be used in displaying the User model. 10 | # These override the definitions on the base UserAdmin 11 | # that reference specific fields on auth.User. 12 | list_display = ('email', 'first_name', 'last_name', 'is_admin') 13 | list_filter = ('is_admin',) 14 | fieldsets = ( 15 | (None, {'fields': ('email', 'password')}), 16 | (_('Personal info'), {'fields': ('first_name', 'last_name')}), 17 | (_('Permissions'), { 18 | 'fields': ( 19 | 'is_active', 20 | 'is_admin', 21 | # 'is_superuser', 22 | 'groups', 23 | 'user_permissions', 24 | ) 25 | }), 26 | (_('Important dates'), {'fields': ('last_login',)}), 27 | ) 28 | # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin 29 | # overrides get_fieldsets to use this attribute when creating a user. 30 | add_fieldsets = ( 31 | (None, { 32 | 'classes': ('wide',), 33 | 'fields': ('email', 'password1', 'password2'), 34 | }), 35 | ) 36 | search_fields = ('email', 'first_name', 'last_name') 37 | ordering = ('email',) 38 | filter_horizontal = ('groups', 'user_permissions',) 39 | 40 | 41 | admin.site.register(User, UserAdmin) 42 | -------------------------------------------------------------------------------- /myproject/accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from myproject.accounts.models import User 4 | 5 | 6 | class TestUser(TestCase): 7 | def setUp(self): 8 | self.user = User.objects.create_user( 9 | email='admin@email.com', 10 | password='demodemo', 11 | first_name='Admin', 12 | last_name='Admin', 13 | ) 14 | self.superuser = User.objects.create_superuser( 15 | email='superadmin@email.com', 16 | password='demodemo' 17 | ) 18 | 19 | def test_user_exists(self): 20 | self.assertTrue(self.user) 21 | 22 | def test_str(self): 23 | self.assertEqual(self.user.email, 'admin@email.com') 24 | 25 | def test_return_attributes(self): 26 | fields = ( 27 | 'id', 28 | 'email', 29 | 'first_name', 30 | 'last_name', 31 | 'password', 32 | 'is_active', 33 | 'is_admin', 34 | 'is_superuser', 35 | 'date_joined', 36 | 'last_login', 37 | ) 38 | 39 | for field in fields: 40 | with self.subTest(): 41 | self.assertTrue(hasattr(User, field)) 42 | 43 | def test_user_is_authenticated(self): 44 | self.assertTrue(self.user.is_authenticated) 45 | 46 | def test_user_is_active(self): 47 | self.assertTrue(self.user.is_active) 48 | 49 | def test_user_is_staff(self): 50 | self.assertFalse(self.user.is_staff) 51 | 52 | def test_user_is_superuser(self): 53 | self.assertFalse(self.user.is_superuser) 54 | 55 | def test_superuser_is_superuser(self): 56 | self.assertTrue(self.superuser.is_superuser) 57 | 58 | def test_user_has_perm(self): 59 | self.assertTrue(self.user.has_perm) 60 | 61 | def test_user_has_module_perms(self): 62 | self.assertTrue(self.user.has_module_perms) 63 | 64 | def test_user_get_full_name(self): 65 | self.assertEqual(self.user.get_full_name(), 'Admin Admin') 66 | 67 | def test_user_get_short_name(self): 68 | self.assertEqual(self.user.get_short_name(), 'Admin') 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-custom-login-email 2 | 3 | Django custom login email tutorial. 4 | 5 | 6 | 7 | 8 | 9 | ## This project was done with: 10 | 11 | * [Python 3.8.9](https://www.python.org/) 12 | * [Django 3.1.8](https://www.djangoproject.com/) 13 | 14 | ## How to run project? 15 | 16 | * Clone this repository. 17 | * Create virtualenv with Python 3. 18 | * Active the virtualenv. 19 | * Install dependences. 20 | * Run the migrations. 21 | 22 | ``` 23 | git clone https://github.com/rg3915/django-custom-login-email.git 24 | cd django-custom-login-email 25 | python -m venv .venv 26 | source .venv/bin/activate 27 | pip install -r requirements.txt 28 | python contrib/env_gen.py 29 | python manage.py migrate 30 | python manage.py createsuperuser --email='admin@email.com' 31 | ``` 32 | 33 | ## Este projeto foi feito com: 34 | 35 | * [Python 3.8.9](https://www.python.org/) 36 | * [Django 3.1.8](https://www.djangoproject.com/) 37 | 38 | ## Como rodar o projeto? 39 | 40 | * Clone esse repositório. 41 | * Crie um virtualenv com Python 3. 42 | * Ative o virtualenv. 43 | * Instale as dependências. 44 | * Rode as migrações. 45 | 46 | ``` 47 | git clone https://github.com/rg3915/django-custom-login-email.git 48 | cd django-custom-login-email 49 | python3 -m venv .venv 50 | source .venv/bin/activate 51 | pip install -r requirements.txt 52 | python contrib/env_gen.py 53 | python manage.py migrate 54 | python manage.py createsuperuser --email='admin@email.com' 55 | ``` 56 | 57 | ## Accounts 58 | 59 | ![img/diagram0.png](img/diagram0.png) 60 | 61 | ## Models 62 | 63 | ![img/models.png](img/models.png) 64 | 65 | ## Links 66 | 67 | https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html#abstractbaseuser 68 | 69 | https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#specifying-a-custom-user-model 70 | 71 | https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#custom-users-admin-full-example 72 | 73 | https://github.com/django/django/blob/main/django/contrib/auth/models.py#L321 74 | 75 | https://github.com/django/django/blob/main/django/contrib/auth/base_user.py#L47 76 | 77 | -------------------------------------------------------------------------------- /myproject/accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.8 on 2021-04-14 07:23 2 | 3 | from django.db import migrations, models 4 | 5 | import myproject.accounts.managers 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('auth', '0012_alter_user_first_name_max_length'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='User', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('password', models.CharField(max_length=128, verbose_name='password')), 22 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 23 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 24 | ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), 25 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 26 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 27 | ('date_joined', models.DateTimeField(auto_now_add=True, verbose_name='date joined')), 28 | ('is_active', models.BooleanField(default=True, verbose_name='active')), 29 | ('is_admin', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='admin status')), 30 | ('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')), 31 | ('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')), 32 | ], 33 | options={ 34 | 'verbose_name': 'user', 35 | 'verbose_name_plural': 'users', 36 | }, 37 | managers=[ 38 | ('objects', myproject.accounts.managers.UserManager()), 39 | ], 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /myproject/accounts/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.contrib.auth.base_user import AbstractBaseUser 4 | from django.contrib.auth.models import PermissionsMixin 5 | from django.core.mail import send_mail 6 | from django.db import models 7 | from django.utils.translation import ugettext_lazy as _ 8 | 9 | from .managers import UserManager 10 | 11 | 12 | class User(AbstractBaseUser, PermissionsMixin): 13 | email = models.EmailField(_('email address'), unique=True) 14 | first_name = models.CharField(_('first name'), max_length=150, blank=True) 15 | last_name = models.CharField(_('last name'), max_length=150, blank=True) 16 | date_joined = models.DateTimeField(_('date joined'), auto_now_add=True) 17 | is_active = models.BooleanField(_('active'), default=True) 18 | is_admin = models.BooleanField( 19 | _('admin status'), 20 | default=False, 21 | help_text=_( 22 | 'Designates whether the user can log into this admin site.'), 23 | ) 24 | 25 | objects = UserManager() 26 | 27 | USERNAME_FIELD = 'email' 28 | REQUIRED_FIELDS = [] 29 | 30 | class Meta: 31 | verbose_name = _('user') 32 | verbose_name_plural = _('users') 33 | 34 | def __str__(self): 35 | return self.email 36 | 37 | def has_perm(self, perm, obj=None): 38 | "Does the user have a specific permission?" 39 | # Simplest possible answer: Yes, always 40 | return True 41 | 42 | def has_module_perms(self, app_label): 43 | "Does the user have permissions to view the app `app_label`?" 44 | # Simplest possible answer: Yes, always 45 | return True 46 | 47 | def get_full_name(self): 48 | ''' 49 | Returns the first_name plus the last_name, with a space in between. 50 | ''' 51 | full_name = '%s %s' % (self.first_name, self.last_name) 52 | return full_name.strip() 53 | 54 | def get_short_name(self): 55 | ''' 56 | Returns the short name for the user. 57 | ''' 58 | return self.first_name 59 | 60 | def email_user(self, subject, message, from_email=None, **kwargs): 61 | ''' 62 | Sends an email to this User. 63 | ''' 64 | send_mail(subject, message, from_email, [self.email], **kwargs) 65 | 66 | @property 67 | def is_staff(self): 68 | "Is the user a member of staff?" 69 | # Simplest possible answer: All admins are staff 70 | return self.is_admin 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .DS_Store 132 | 133 | media/ 134 | staticfiles/ 135 | .idea 136 | .ipynb_checkpoints/ 137 | .vscode 138 | -------------------------------------------------------------------------------- /myproject/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for myproject project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.1.8. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | from decouple import Csv, config 16 | from dj_database_url import parse as dburl 17 | 18 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 19 | BASE_DIR = Path(__file__).resolve().parent.parent 20 | 21 | 22 | # Quick-start development settings - unsuitable for production 23 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 24 | 25 | # SECURITY WARNING: keep the secret key used in production secret! 26 | SECRET_KEY = config('SECRET_KEY') 27 | 28 | # SECURITY WARNING: don't run with debug turned on in production! 29 | DEBUG = config('DEBUG', default=False, cast=bool) 30 | 31 | ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv()) 32 | 33 | AUTH_USER_MODEL = 'accounts.User' 34 | 35 | # Application definition 36 | 37 | INSTALLED_APPS = [ 38 | 'django.contrib.admin', 39 | 'django.contrib.auth', 40 | 'django.contrib.contenttypes', 41 | 'django.contrib.sessions', 42 | 'django.contrib.messages', 43 | 'django.contrib.staticfiles', 44 | # thirty apps 45 | 'django_extensions', 46 | # my apps 47 | 'myproject.accounts', 48 | 'myproject.core', 49 | 'myproject.crm', 50 | ] 51 | 52 | MIDDLEWARE = [ 53 | 'django.middleware.security.SecurityMiddleware', 54 | 'django.contrib.sessions.middleware.SessionMiddleware', 55 | 'django.middleware.common.CommonMiddleware', 56 | 'django.middleware.csrf.CsrfViewMiddleware', 57 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 58 | 'django.contrib.messages.middleware.MessageMiddleware', 59 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 60 | ] 61 | 62 | ROOT_URLCONF = 'myproject.urls' 63 | 64 | TEMPLATES = [ 65 | { 66 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 67 | 'DIRS': [], 68 | 'APP_DIRS': True, 69 | 'OPTIONS': { 70 | 'context_processors': [ 71 | 'django.template.context_processors.debug', 72 | 'django.template.context_processors.request', 73 | 'django.contrib.auth.context_processors.auth', 74 | 'django.contrib.messages.context_processors.messages', 75 | ], 76 | }, 77 | }, 78 | ] 79 | 80 | WSGI_APPLICATION = 'myproject.wsgi.application' 81 | 82 | 83 | # Database 84 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases 85 | 86 | default_dburl = 'sqlite:///' + str(BASE_DIR / 'db.sqlite3') 87 | DATABASES = { 88 | 'default': config('DATABASE_URL', default=default_dburl, cast=dburl), 89 | } 90 | 91 | 92 | # Password validation 93 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 94 | 95 | AUTH_PASSWORD_VALIDATORS = [ 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 101 | }, 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 107 | }, 108 | ] 109 | 110 | 111 | # Internationalization 112 | # https://docs.djangoproject.com/en/3.1/topics/i18n/ 113 | 114 | LANGUAGE_CODE = 'pt-br' 115 | 116 | TIME_ZONE = 'America/Sao_Paulo' 117 | 118 | USE_I18N = True 119 | 120 | USE_L10N = True 121 | 122 | USE_TZ = True 123 | 124 | USE_THOUSAND_SEPARATOR = True 125 | 126 | DECIMAL_SEPARATOR = ',' 127 | 128 | 129 | # Static files (CSS, JavaScript, Images) 130 | # https://docs.djangoproject.com/en/3.1/howto/static-files/ 131 | 132 | STATIC_URL = '/static/' 133 | STATIC_ROOT = BASE_DIR.joinpath('staticfiles') 134 | 135 | LOGIN_URL = '/admin/login/' 136 | LOGIN_REDIRECT_URL = 'core:index' 137 | # LOGOUT_REDIRECT_URL = 'core:index' 138 | -------------------------------------------------------------------------------- /passo-a-passo.md: -------------------------------------------------------------------------------- 1 | # django-custom-login-email 2 | 3 | 4 | ### Instale as dependências 5 | 6 | ``` 7 | python -m venv .venv 8 | source .venv/bin/activate 9 | pip install -U pip 10 | pip install Django==3.1.8 dj-database-url django-extensions python-decouple 11 | 12 | pip freeze | grep Django==3.1.8 >> requirements.txt 13 | pip freeze | grep dj-database-url >> requirements.txt 14 | pip freeze | grep django-extensions >> requirements.txt 15 | pip freeze | grep python-decouple >> requirements.txt 16 | 17 | cat requirements.txt 18 | ``` 19 | 20 | 21 | ### Criando um .gitignore 22 | 23 | Veja no repositório do projeto. 24 | 25 | https://github.com/rg3915/django-custom-login-email/blob/main/.gitignore 26 | 27 | ### Gere um arquivo .env 28 | 29 | Copiar o conteúdo de `env_gen.py` 30 | 31 | https://github.com/rg3915/django-custom-login-email/blob/main/contrib/env_gen.py 32 | 33 | ``` 34 | mkdir contrib 35 | touch contrib/env_gen.py 36 | 37 | python contrib/env_gen.py 38 | 39 | cat .env 40 | ``` 41 | 42 | 43 | ### Criando um projeto 44 | 45 | ``` 46 | django-admin.py startproject myproject . 47 | ``` 48 | 49 | ### Criando uma app 50 | 51 | ``` 52 | cd myproject 53 | python ../manage.py startapp accounts 54 | python ../manage.py startapp core 55 | python ../manage.py startapp crm 56 | ``` 57 | 58 | #### Deletando alguns arquivos 59 | 60 | ``` 61 | rm -f core/admin.py 62 | rm -f core/models.py 63 | ``` 64 | 65 | #### Criando alguns arquivos 66 | 67 | ``` 68 | touch accounts/managers.py 69 | touch accounts/urls.py 70 | touch core/urls.py 71 | ``` 72 | 73 | ### Editar settings.py 74 | 75 | ```python 76 | # settings.py 77 | from pathlib import Path 78 | 79 | from decouple import Csv, config 80 | from dj_database_url import parse as dburl 81 | 82 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 83 | BASE_DIR = Path(__file__).resolve().parent.parent 84 | 85 | 86 | # Quick-start development settings - unsuitable for production 87 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 88 | 89 | # SECURITY WARNING: keep the secret key used in production secret! 90 | SECRET_KEY = config('SECRET_KEY') 91 | 92 | # SECURITY WARNING: don't run with debug turned on in production! 93 | DEBUG = config('DEBUG', default=False, cast=bool) 94 | 95 | ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv()) 96 | 97 | AUTH_USER_MODEL = 'accounts.User' 98 | 99 | ... 100 | 101 | INSTALLED_APPS = [ 102 | ... 103 | # thirty apps 104 | 'django_extensions', 105 | # my apps 106 | 'myproject.accounts', 107 | 'myproject.core', 108 | 'myproject.crm', 109 | ] 110 | 111 | ... 112 | 113 | default_dburl = 'sqlite:///' + str(BASE_DIR / 'db.sqlite3') 114 | DATABASES = { 115 | 'default': config('DATABASE_URL', default=default_dburl, cast=dburl), 116 | } 117 | 118 | ... 119 | 120 | LANGUAGE_CODE = 'pt-br' 121 | 122 | TIME_ZONE = 'America/Sao_Paulo' 123 | 124 | ... 125 | 126 | USE_THOUSAND_SEPARATOR = True 127 | 128 | DECIMAL_SEPARATOR = ',' 129 | 130 | ... 131 | 132 | STATIC_URL = '/static/' 133 | STATIC_ROOT = BASE_DIR.joinpath('staticfiles') 134 | 135 | LOGIN_URL = '/admin/login/' 136 | LOGIN_REDIRECT_URL = 'core:index' 137 | # LOGOUT_REDIRECT_URL = 'core:index' 138 | ``` 139 | 140 | ### Editar urls.py 141 | 142 | ```python 143 | # urls.py 144 | from django.contrib import admin 145 | from django.urls import include, path 146 | 147 | urlpatterns = [ 148 | path('', include('myproject.core.urls', namespace='core')), 149 | path('accounts/', include('myproject.accounts.urls')), # without namespace 150 | path('admin/', admin.site.urls), 151 | ] 152 | ``` 153 | 154 | ### Editar core/urls.py 155 | 156 | ```python 157 | # core/urls.py 158 | from django.urls import path 159 | 160 | from myproject.core import views as v 161 | 162 | app_name = 'core' 163 | 164 | 165 | urlpatterns = [ 166 | path('', v.index, name='index'), 167 | ] 168 | ``` 169 | 170 | ### Editar core/views.py 171 | 172 | ```python 173 | # core/views.py 174 | from django.contrib.auth.decorators import login_required 175 | from django.http import HttpResponse 176 | from django.shortcuts import render 177 | 178 | 179 | # @login_required 180 | def index(request): 181 | return HttpResponse('

Django

Página simples.

') 182 | ``` 183 | 184 | ### Editar accounts/models.py 185 | 186 | ```python 187 | # accounts/models.py 188 | from __future__ import unicode_literals 189 | 190 | from django.contrib.auth.base_user import AbstractBaseUser 191 | from django.contrib.auth.models import PermissionsMixin 192 | from django.core.mail import send_mail 193 | from django.db import models 194 | from django.utils.translation import ugettext_lazy as _ 195 | 196 | from .managers import UserManager 197 | 198 | 199 | class User(AbstractBaseUser, PermissionsMixin): 200 | email = models.EmailField(_('email address'), unique=True) 201 | first_name = models.CharField(_('first name'), max_length=150, blank=True) 202 | last_name = models.CharField(_('last name'), max_length=150, blank=True) 203 | date_joined = models.DateTimeField(_('date joined'), auto_now_add=True) 204 | is_active = models.BooleanField(_('active'), default=True) 205 | is_admin = models.BooleanField( 206 | _('admin status'), 207 | default=False, 208 | help_text=_( 209 | 'Designates whether the user can log into this admin site.'), 210 | ) 211 | 212 | objects = UserManager() 213 | 214 | USERNAME_FIELD = 'email' 215 | REQUIRED_FIELDS = [] 216 | 217 | class Meta: 218 | verbose_name = _('user') 219 | verbose_name_plural = _('users') 220 | 221 | def __str__(self): 222 | return self.email 223 | 224 | def has_perm(self, perm, obj=None): 225 | "Does the user have a specific permission?" 226 | # Simplest possible answer: Yes, always 227 | return True 228 | 229 | def has_module_perms(self, app_label): 230 | "Does the user have permissions to view the app `app_label`?" 231 | # Simplest possible answer: Yes, always 232 | return True 233 | 234 | def get_full_name(self): 235 | ''' 236 | Returns the first_name plus the last_name, with a space in between. 237 | ''' 238 | full_name = '%s %s' % (self.first_name, self.last_name) 239 | return full_name.strip() 240 | 241 | def get_short_name(self): 242 | ''' 243 | Returns the short name for the user. 244 | ''' 245 | return self.first_name 246 | 247 | def email_user(self, subject, message, from_email=None, **kwargs): 248 | ''' 249 | Sends an email to this User. 250 | ''' 251 | send_mail(subject, message, from_email, [self.email], **kwargs) 252 | 253 | @property 254 | def is_staff(self): 255 | "Is the user a member of staff?" 256 | # Simplest possible answer: All admins are staff 257 | return self.is_admin 258 | ``` 259 | 260 | ### Editar accounts/managers.py 261 | 262 | ```python 263 | # accounts/managers.py 264 | from django.contrib.auth.base_user import BaseUserManager 265 | 266 | 267 | class UserManager(BaseUserManager): 268 | use_in_migrations = True 269 | 270 | def _create_user(self, email, password, **extra_fields): 271 | """ 272 | Creates and saves a User with the given email and password. 273 | """ 274 | if not email: 275 | raise ValueError('The given email must be set') 276 | email = self.normalize_email(email) 277 | user = self.model(email=email, **extra_fields) 278 | user.set_password(password) 279 | user.save(using=self._db) 280 | return user 281 | 282 | def create_user(self, email, password=None, **extra_fields): 283 | extra_fields.setdefault('is_superuser', False) 284 | return self._create_user(email, password, **extra_fields) 285 | 286 | def create_superuser(self, email, password, **extra_fields): 287 | extra_fields.setdefault('is_admin', True) 288 | extra_fields.setdefault('is_superuser', True) 289 | 290 | if extra_fields.get('is_admin') is not True: 291 | raise ValueError('Superuser must have is_admin=True.') 292 | if extra_fields.get('is_superuser') is not True: 293 | raise ValueError('Superuser must have is_superuser=True.') 294 | 295 | return self._create_user(email, password, **extra_fields) 296 | ``` 297 | 298 | ### Editar accounts/admin.py 299 | 300 | ```python 301 | # accounts/admin.py 302 | from django.contrib import admin 303 | from django.contrib.auth.admin import UserAdmin as BaseUserAdmin 304 | from django.utils.translation import gettext_lazy as _ 305 | 306 | from .models import User 307 | 308 | 309 | class UserAdmin(BaseUserAdmin): 310 | # The fields to be used in displaying the User model. 311 | # These override the definitions on the base UserAdmin 312 | # that reference specific fields on auth.User. 313 | list_display = ('email', 'first_name', 'last_name', 'is_admin') 314 | list_filter = ('is_admin',) 315 | fieldsets = ( 316 | (None, {'fields': ('email', 'password')}), 317 | (_('Personal info'), {'fields': ('first_name', 'last_name')}), 318 | (_('Permissions'), { 319 | 'fields': ( 320 | 'is_active', 321 | 'is_admin', 322 | # 'is_superuser', 323 | 'groups', 324 | 'user_permissions', 325 | ) 326 | }), 327 | (_('Important dates'), {'fields': ('last_login',)}), 328 | ) 329 | # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin 330 | # overrides get_fieldsets to use this attribute when creating a user. 331 | add_fieldsets = ( 332 | (None, { 333 | 'classes': ('wide',), 334 | 'fields': ('email', 'password1', 'password2'), 335 | }), 336 | ) 337 | search_fields = ('email', 'first_name', 'last_name') 338 | ordering = ('email',) 339 | filter_horizontal = ('groups', 'user_permissions',) 340 | 341 | 342 | admin.site.register(User, UserAdmin) 343 | ``` 344 | 345 | ### Editar accounts/urls.py 346 | 347 | ```python 348 | # accounts/urls.py 349 | from django.contrib.auth.views import LoginView, LogoutView 350 | from django.urls import path 351 | 352 | # Read https://github.com/rg3915/django-auth-tutorial 353 | 354 | urlpatterns = [ 355 | path('login/', LoginView.as_view(), name='login'), 356 | path('logout/', LogoutView.as_view(), name='logout'), 357 | ] 358 | ``` 359 | 360 | ### Editar accounts/tests.py 361 | 362 | ```python 363 | # accounts/tests.py 364 | from django.test import TestCase 365 | 366 | from myproject.accounts.models import User 367 | 368 | 369 | class TestUser(TestCase): 370 | def setUp(self): 371 | self.user = User.objects.create_user( 372 | email='admin@email.com', 373 | password='demodemo', 374 | first_name='Admin', 375 | last_name='Admin', 376 | ) 377 | self.superuser = User.objects.create_superuser( 378 | email='superadmin@email.com', 379 | password='demodemo' 380 | ) 381 | 382 | def test_user_exists(self): 383 | self.assertTrue(self.user) 384 | 385 | def test_str(self): 386 | self.assertEqual(self.user.email, 'admin@email.com') 387 | 388 | def test_return_attributes(self): 389 | fields = ( 390 | 'id', 391 | 'email', 392 | 'first_name', 393 | 'last_name', 394 | 'password', 395 | 'is_active', 396 | 'is_admin', 397 | 'is_superuser', 398 | 'date_joined', 399 | 'last_login', 400 | ) 401 | 402 | for field in fields: 403 | with self.subTest(): 404 | self.assertTrue(hasattr(User, field)) 405 | 406 | def test_user_is_authenticated(self): 407 | self.assertTrue(self.user.is_authenticated) 408 | 409 | def test_user_is_active(self): 410 | self.assertTrue(self.user.is_active) 411 | 412 | def test_user_is_staff(self): 413 | self.assertFalse(self.user.is_staff) 414 | 415 | def test_user_is_superuser(self): 416 | self.assertFalse(self.user.is_superuser) 417 | 418 | def test_superuser_is_superuser(self): 419 | self.assertTrue(self.superuser.is_superuser) 420 | 421 | def test_user_has_perm(self): 422 | self.assertTrue(self.user.has_perm) 423 | 424 | def test_user_has_module_perms(self): 425 | self.assertTrue(self.user.has_module_perms) 426 | 427 | def test_user_get_full_name(self): 428 | self.assertEqual(self.user.get_full_name(), 'Admin Admin') 429 | 430 | def test_user_get_short_name(self): 431 | self.assertEqual(self.user.get_short_name(), 'Admin') 432 | ``` 433 | 434 | ### Editar crm/models.py 435 | 436 | ```python 437 | # crm/models.py 438 | from django.conf import settings 439 | from django.db import models 440 | 441 | 442 | class Phone(models.Model): 443 | phone = models.CharField(max_length=13) 444 | user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) # noqa E501 445 | 446 | class Meta: 447 | ordering = ('-pk',) 448 | verbose_name = 'telefone' 449 | verbose_name_plural = 'telefones' 450 | 451 | def __str__(self): 452 | return self.user.email 453 | ``` 454 | 455 | ### Editar crm/admin.py 456 | 457 | ```python 458 | # crm/admin.py 459 | from django.contrib import admin 460 | 461 | from .models import Phone 462 | 463 | 464 | @admin.register(Phone) 465 | class PhoneAdmin(admin.ModelAdmin): 466 | list_display = ('__str__', 'phone') 467 | search_fields = ('user__email',) 468 | ``` 469 | 470 | ### Editar crm/tests.py 471 | 472 | ```python 473 | # crm/tests.py 474 | from django.test import TestCase 475 | 476 | from myproject.accounts.models import User 477 | from myproject.crm.models import Phone 478 | 479 | 480 | class TestUser(TestCase): 481 | def setUp(self): 482 | self.user = User.objects.create_user( 483 | email='admin@email.com', 484 | password='demodemo', 485 | first_name='Admin', 486 | last_name='Admin', 487 | ) 488 | self.phone = Phone.objects.create( 489 | phone='9876-54321', 490 | user=self.user 491 | ) 492 | 493 | def test_phone_exists(self): 494 | self.assertTrue(self.phone) 495 | 496 | def test_str(self): 497 | self.assertEqual(self.user.email, 'admin@email.com') 498 | ``` 499 | 500 | 501 | 502 | 503 | ``` 504 | python manage.py makemigrations 505 | python manage.py migrate 506 | 507 | python manage.py createsuperuser --email='admin@email.com' 508 | 509 | python manage.py test 510 | ``` 511 | --------------------------------------------------------------------------------