Skip to content

Commit

Permalink
criado usuario personalizado e ajustado painel de admin
Browse files Browse the repository at this point in the history
close #44
  • Loading branch information
thiago-garcia committed Mar 30, 2024
1 parent aef2698 commit f5fce28
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[flake8]
max-line-length = 120
exclude = .venv, .idea, .vscode
exclude = .venv, .idea, .vscode, *migrations
217 changes: 217 additions & 0 deletions pypro/base/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
from django.conf import settings
from django.contrib import admin, messages
from django.contrib.admin.options import IS_POPUP_VAR
from django.contrib.admin.utils import unquote
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import (
AdminPasswordChangeForm,
UserChangeForm,
UserCreationForm,
)
from django.core.exceptions import PermissionDenied
from django.db import router, transaction
from django.http import Http404, HttpResponseRedirect
from django.template.response import TemplateResponse
from django.urls import path, reverse
from django.utils.decorators import method_decorator
from django.utils.html import escape
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters

from pypro.base.models import User

csrf_protect_m = method_decorator(csrf_protect)
sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())


@admin.register(User)
class UserAdmin(admin.ModelAdmin):
add_form_template = "admin/auth/user/add_form.html"
change_user_password_template = None
fieldsets = (
(None, {"fields": ("first_name", "email", "password")}),
(
_("Permissions"),
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
"groups",
"user_permissions",
),
},
),
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
)
add_fieldsets = (
(
None,
{
"classes": ("wide",),
"fields": ("first_name", "email", "password1", "password2"),
},
),
)
form = UserChangeForm
add_form = UserCreationForm
change_password_form = AdminPasswordChangeForm
list_display = ("email", "first_name", "is_staff")
list_filter = ("is_staff", "is_superuser", "is_active", "groups")
search_fields = ("first_name", "email")
ordering = ("first_name",)
filter_horizontal = (
"groups",
"user_permissions",
)

def get_fieldsets(self, request, obj=None):
if not obj:
return self.add_fieldsets
return super().get_fieldsets(request, obj)

Check warning on line 73 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L71-L73

Added lines #L71 - L73 were not covered by tests

def get_form(self, request, obj=None, **kwargs):
"""
Use special form during user creation
"""
defaults = {}
if obj is None:
defaults["form"] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)

Check warning on line 83 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L79-L83

Added lines #L79 - L83 were not covered by tests

def get_urls(self):
return [
path(
"<id>/password/",
self.admin_site.admin_view(self.user_change_password),
name="auth_user_password_change",
),
] + super().get_urls()

# RemovedInDjango60Warning: when the deprecation ends, replace with:
# def lookup_allowed(self, lookup, value, request):
def lookup_allowed(self, lookup, value, request=None):
# Don't allow lookups involving passwords.
return not lookup.startswith("password") and super().lookup_allowed(

Check warning on line 98 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L98

Added line #L98 was not covered by tests
lookup, value, request
)

@sensitive_post_parameters_m
@csrf_protect_m
def add_view(self, request, form_url="", extra_context=None):
with transaction.atomic(using=router.db_for_write(self.model)):
return self._add_view(request, form_url, extra_context)

Check warning on line 106 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L105-L106

Added lines #L105 - L106 were not covered by tests

def _add_view(self, request, form_url="", extra_context=None):
# It's an error for a user to have add permission but NOT change
# permission for users. If we allowed such users to add users, they
# could create superusers, which would mean they would essentially have
# the permission to change users. To avoid the problem entirely, we
# disallow users from adding users if they don't have change
# permission.
if not self.has_change_permission(request):
if self.has_add_permission(request) and settings.DEBUG:

Check warning on line 116 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L115-L116

Added lines #L115 - L116 were not covered by tests
# Raise Http404 in debug mode so that the user gets a helpful
# error message.
raise Http404(

Check warning on line 119 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L119

Added line #L119 was not covered by tests
'Your user does not have the "Change user" permission. In '
"order to add users, Django requires that your user "
'account have both the "Add user" and "Change user" '
"permissions set."
)
raise PermissionDenied
if extra_context is None:
extra_context = {}
username_field = self.opts.get_field(self.model.USERNAME_FIELD)
defaults = {

Check warning on line 129 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L125-L129

Added lines #L125 - L129 were not covered by tests
"auto_populated_fields": (),
"username_help_text": username_field.help_text,
}
extra_context.update(defaults)
return super().add_view(request, form_url, extra_context)

Check warning on line 134 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L133-L134

Added lines #L133 - L134 were not covered by tests

@sensitive_post_parameters_m
def user_change_password(self, request, id, form_url=""):
user = self.get_object(request, unquote(id))
if not self.has_change_permission(request, user):
raise PermissionDenied
if user is None:
raise Http404(

Check warning on line 142 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L138-L142

Added lines #L138 - L142 were not covered by tests
_("%(name)s object with primary key %(key)r does not exist.")
% {
"name": self.opts.verbose_name,
"key": escape(id),
}
)
if request.method == "POST":
form = self.change_password_form(user, request.POST)
if form.is_valid():
form.save()
change_message = self.construct_change_message(request, form, None)
self.log_change(request, user, change_message)
msg = gettext("Password changed successfully.")
messages.success(request, msg)
update_session_auth_hash(request, form.user)
return HttpResponseRedirect(

Check warning on line 158 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L149-L158

Added lines #L149 - L158 were not covered by tests
reverse(
"%s:%s_%s_change"
% (
self.admin_site.name,
user._meta.app_label,
user._meta.model_name,
),
args=(user.pk,),
)
)
else:
form = self.change_password_form(user)

Check warning on line 170 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L170

Added line #L170 was not covered by tests

fieldsets = [(None, {"fields": list(form.base_fields)})]
admin_form = admin.helpers.AdminForm(form, fieldsets, {})

Check warning on line 173 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L172-L173

Added lines #L172 - L173 were not covered by tests

context = {

Check warning on line 175 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L175

Added line #L175 was not covered by tests
"title": _("Change password: %s") % escape(user.get_username()),
"adminForm": admin_form,
"form_url": form_url,
"form": form,
"is_popup": (IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET),
"is_popup_var": IS_POPUP_VAR,
"add": True,
"change": False,
"has_delete_permission": False,
"has_change_permission": True,
"has_absolute_url": False,
"opts": self.opts,
"original": user,
"save_as": False,
"show_save": True,
**self.admin_site.each_context(request),
}

request.current_app = self.admin_site.name

Check warning on line 194 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L194

Added line #L194 was not covered by tests

return TemplateResponse(

Check warning on line 196 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L196

Added line #L196 was not covered by tests
request,
self.change_user_password_template
or "admin/auth/user/change_password.html",
context,
)

def response_add(self, request, obj, post_url_continue=None):
"""
Determine the HttpResponse for the add_view stage. It mostly defers to
its superclass implementation but is customized because the User model
has a slightly different workflow.
"""
# We should allow further modification of the user just added i.e. the
# 'Save' button should behave like the 'Save and continue editing'
# button except in two scenarios:
# * The user has pressed the 'Save and add another' button
# * We are adding a user in a popup
if "_addanother" not in request.POST and IS_POPUP_VAR not in request.POST:
request.POST = request.POST.copy()
request.POST["_continue"] = 1
return super().response_add(request, obj, post_url_continue)

Check warning on line 217 in pypro/base/admin.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/admin.py#L214-L217

Added lines #L214 - L217 were not covered by tests
40 changes: 40 additions & 0 deletions pypro/base/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Generated by Django 5.0.3 on 2024-03-30 18:42

import django.utils.timezone
import pypro.base.models
from django.db import migrations, models

Check warning on line 5 in pypro/base/migrations/0001_initial.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/migrations/0001_initial.py#L3-L5

Added lines #L3 - L5 were not covered by tests


class Migration(migrations.Migration):

Check warning on line 8 in pypro/base/migrations/0001_initial.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/migrations/0001_initial.py#L8

Added line #L8 was not covered by tests

initial = True

Check warning on line 10 in pypro/base/migrations/0001_initial.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/migrations/0001_initial.py#L10

Added line #L10 was not covered by tests

dependencies = [

Check warning on line 12 in pypro/base/migrations/0001_initial.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/migrations/0001_initial.py#L12

Added line #L12 was not covered by tests
('auth', '0012_alter_user_first_name_max_length'),
]

operations = [
migrations.CreateModel(
name='User',
fields=[

Check warning on line 19 in pypro/base/migrations/0001_initial.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/migrations/0001_initial.py#L16-L19

Added lines #L16 - L19 were not covered by tests
('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')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('email', models.EmailField(max_length=254, unique=True, 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')),
('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',
},
managers=[
('objects', pypro.base.models.UserManager()),
],
),
]
Empty file.
99 changes: 99 additions & 0 deletions pypro/base/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import PermissionsMixin
from django.core.mail import send_mail
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _


class UserManager(BaseUserManager):
use_in_migrations = True

def _create_user(self, email, password, **extra_fields):
"""
Create and save a user with the given email, and password.
"""
if not email:
raise ValueError("The given email must be set")
email = self.normalize_email(email)
# Lookup the real model class from the global app registry so this
# manager method can be used in migrations. This is fine because
# managers are by definition working on the real model.
# GlobalUserModel = apps.get_model(
# self.model._meta.app_label, self.model._meta.object_name
# )
user = self.model(email=email, **extra_fields)
user.password = make_password(password)
user.save(using=self._db)
return user

Check warning on line 29 in pypro/base/models.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/models.py#L26-L29

Added lines #L26 - L29 were not covered by tests

def create_user(self, email, password=None, **extra_fields):
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
return self._create_user(email, password, **extra_fields)

Check warning on line 34 in pypro/base/models.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/models.py#L32-L34

Added lines #L32 - L34 were not covered by tests

def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)

Check warning on line 38 in pypro/base/models.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/models.py#L37-L38

Added lines #L37 - L38 were not covered by tests

if extra_fields.get("is_staff") is not True:
raise ValueError("Superuser must have is_staff=True.")
if extra_fields.get("is_superuser") is not True:
raise ValueError("Superuser must have is_superuser=True.")

Check warning on line 43 in pypro/base/models.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/models.py#L40-L43

Added lines #L40 - L43 were not covered by tests

return self._create_user(email, password, **extra_fields)

Check warning on line 45 in pypro/base/models.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/models.py#L45

Added line #L45 was not covered by tests


class User(AbstractBaseUser, PermissionsMixin):
"""
App base User class
Email and password are required. Other fields are optional.
"""

first_name = models.CharField(_("first name"), max_length=150, blank=True)
email = models.EmailField(_("email address"), unique=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
is_active = models.BooleanField(
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
)
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)

objects = UserManager()

EMAIL_FIELD = "email"
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []

class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")

def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)

Check warning on line 84 in pypro/base/models.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/models.py#L83-L84

Added lines #L83 - L84 were not covered by tests

def get_full_name(self):
"""
Return the first_name plus the last_name, with a space in between.
"""
full_name = "%s" % (self.first_name)
return full_name.strip()

Check warning on line 91 in pypro/base/models.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/models.py#L90-L91

Added lines #L90 - L91 were not covered by tests

def get_short_name(self):
"""Return the short name for the user."""
return self.first_name

Check warning on line 95 in pypro/base/models.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/models.py#L95

Added line #L95 was not covered by tests

def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)

Check warning on line 99 in pypro/base/models.py

View check run for this annotation

Codecov / codecov/patch

pypro/base/models.py#L99

Added line #L99 was not covered by tests
2 changes: 2 additions & 0 deletions pypro/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@

ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())

AUTH_USER_MODEL = 'base.User'


# Application definition

Expand Down

0 comments on commit f5fce28

Please sign in to comment.