mirror of https://github.com/snachodog/mybuddy.git
Create a user add management command (#534)
* feat: Create management command to add a user * feat: Test user create management command * feat: Remove unnecessary args from createuser command * fix: remove in-active arg from createuser command * feat: Add doc to createuser command Co-authored-by: Christopher C. Wells <git-2022@chris-wells.net>
This commit is contained in:
parent
092fde5eef
commit
74582effb1
|
@ -0,0 +1,164 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Management utility to create users
|
||||
|
||||
Example usage:
|
||||
|
||||
manage.py createuser \
|
||||
--username test \
|
||||
--email test@test.test \
|
||||
--is-staff
|
||||
"""
|
||||
import sys
|
||||
import getpass
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
from django.core import exceptions
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import DEFAULT_DB_ALIAS
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.text import capfirst
|
||||
|
||||
|
||||
class NotRunningInTTYException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Used to create a user"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.UserModel = get_user_model()
|
||||
self.username_field = self.UserModel._meta.get_field(
|
||||
self.UserModel.USERNAME_FIELD
|
||||
)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
f"--{self.UserModel.USERNAME_FIELD}",
|
||||
help="Specifies the login for a user.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--email",
|
||||
dest="email",
|
||||
default="",
|
||||
help="Specifies the email for the user. Optional.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--password",
|
||||
dest="password",
|
||||
help="Specifies the password for the user. Optional.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--is-staff",
|
||||
dest="is_staff",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Specifies the staff status for the user. Default is False.",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
username = options.get(self.UserModel.USERNAME_FIELD)
|
||||
password = options.get("password")
|
||||
|
||||
user_data = {}
|
||||
user_password = ""
|
||||
verbose_field_name = self.username_field.verbose_name
|
||||
|
||||
try:
|
||||
error_msg = self._validate_username(
|
||||
username, verbose_field_name, DEFAULT_DB_ALIAS
|
||||
)
|
||||
if error_msg:
|
||||
raise CommandError(error_msg)
|
||||
|
||||
user_data[self.UserModel.USERNAME_FIELD] = username
|
||||
|
||||
# Prompt for a password interactively (if password not set via arg)
|
||||
while password is None:
|
||||
password = getpass.getpass()
|
||||
password2 = getpass.getpass("Password (again): ")
|
||||
|
||||
if password.strip() == "":
|
||||
self.stderr.write("Error: Blank passwords aren't allowed.")
|
||||
password = None
|
||||
# Don't validate blank passwords.
|
||||
continue
|
||||
|
||||
if password != password2:
|
||||
self.stderr.write("Error: Your passwords didn't match.")
|
||||
password = None
|
||||
password2 = None
|
||||
# Don't validate passwords that don't match.
|
||||
continue
|
||||
|
||||
try:
|
||||
validate_password(password2, self.UserModel(**user_data))
|
||||
except exceptions.ValidationError as err:
|
||||
self.stderr.write("\n".join(err.messages))
|
||||
response = input(
|
||||
"Bypass password validation and create user anyway? [y/N]: "
|
||||
)
|
||||
if response.lower() != "y":
|
||||
password = None
|
||||
password2 = None
|
||||
continue
|
||||
|
||||
user_password = password
|
||||
|
||||
user = self.UserModel._default_manager.db_manager(
|
||||
DEFAULT_DB_ALIAS
|
||||
).create_user(**user_data, password=user_password)
|
||||
user.email = options.get("email")
|
||||
user.is_staff = options.get("is_staff")
|
||||
user.save()
|
||||
|
||||
if options.get("verbosity") > 0:
|
||||
self.stdout.write(f"User {username} created successfully.")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
self.stderr.write("\nOperation cancelled.")
|
||||
sys.exit(1)
|
||||
except exceptions.ValidationError as e:
|
||||
raise CommandError("; ".join(e.messages))
|
||||
except NotRunningInTTYException:
|
||||
self.stdout.write(
|
||||
"User creation skipped due to not running in a TTY. "
|
||||
"You can run `manage.py createuser` in your project "
|
||||
"to create one manually."
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def username_is_unique(self):
|
||||
"""
|
||||
Check if username is unique.
|
||||
"""
|
||||
if self.username_field.unique:
|
||||
return True
|
||||
return any(
|
||||
len(unique_constraint.fields) == 1
|
||||
and unique_constraint.fields[0] == self.username_field.name
|
||||
for unique_constraint in self.UserModel._meta.total_unique_constraints
|
||||
)
|
||||
|
||||
def _validate_username(self, username, verbose_field_name, database):
|
||||
"""
|
||||
Validate username. If invalid, return a string error message.
|
||||
"""
|
||||
if self.username_is_unique:
|
||||
try:
|
||||
self.UserModel._default_manager.db_manager(database).get_by_natural_key(
|
||||
username
|
||||
)
|
||||
except self.UserModel.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
return f"Error: The {verbose_field_name} is already taken."
|
||||
if not username:
|
||||
return f"{capfirst(verbose_field_name)} cannot be blank."
|
||||
try:
|
||||
self.username_field.clean(username, None)
|
||||
except exceptions.ValidationError as e:
|
||||
return "; ".join(e.messages)
|
|
@ -22,3 +22,24 @@ class CommandsTestCase(TransactionTestCase):
|
|||
call_command("reset", verbosity=0, interactive=False)
|
||||
self.assertIsInstance(User.objects.get(username="admin"), User)
|
||||
self.assertEqual(Child.objects.count(), 1)
|
||||
|
||||
def test_createuser(self):
|
||||
call_command(
|
||||
"createuser",
|
||||
username="test",
|
||||
email="test@test.test",
|
||||
password="test",
|
||||
verbosity=0,
|
||||
)
|
||||
self.assertIsInstance(User.objects.get(username="test"), User)
|
||||
self.assertFalse(User.objects.filter(username="test", is_staff=True))
|
||||
call_command(
|
||||
"createuser",
|
||||
"--is-staff",
|
||||
username="testadmin",
|
||||
email="testadmin@testadmin.testadmin",
|
||||
password="test",
|
||||
verbosity=0,
|
||||
)
|
||||
self.assertIsInstance(User.objects.get(username="testadmin"), User)
|
||||
self.assertTrue(User.objects.filter(username="testadmin", is_staff=True))
|
||||
|
|
|
@ -17,3 +17,39 @@
|
|||
<video style="max-width: 320px;" autoplay controls loop muted playsinline>
|
||||
<source src="../../assets/videos/user_password.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
## Creating a User from the Command Line
|
||||
|
||||
There are 2 ways you can create a user from the command line:
|
||||
|
||||
1. Passing user's password as an argument:
|
||||
|
||||
```shell
|
||||
python manage.py createuser --username <username> --password <password>
|
||||
```
|
||||
|
||||
2. Interactively setting user's password:
|
||||
|
||||
```shell
|
||||
python manage.py createuser --username <username>
|
||||
```
|
||||
|
||||
You will then be prompted to enter and confirm a password.
|
||||
|
||||
- If you want to make the user a staff, you can append the `--is-staff` argument:
|
||||
|
||||
```shell
|
||||
python manage.py createuser --username <username> --is-staff
|
||||
```
|
||||
|
||||
- Another argument you can use with this command is `--email`
|
||||
|
||||
```shell
|
||||
python manage.py createuser --username <username> --email <email>
|
||||
```
|
||||
|
||||
- To get a list of supported commands:
|
||||
|
||||
```shell
|
||||
python manage.py createuser --help
|
||||
```
|
||||
|
|
Loading…
Reference in New Issue