Repository: saleor/saleor

Issue:
Bug: `craetesuperuser` doesn't work
### What are you trying to achieve?

I'm trying to create new superuser by using the `./manage.py createsuperuser` command.

### Steps to reproduce the problem

1. Clone [saleor-platform](https://github.com/saleor/saleor-platform)
2. Checkout `saleor` to `main` branch
3. Build docker images: `docker compose build api`
4. Start Saleor: `docker compose -d up api worker`
5. Run migrations: `docker-compose run --rm api python3 manage.py migrate`
6. Populate database: `docker-compose run --rm api python3 manage.py populatedb`
7. Run `createsuperuser` command: `docker-compose run --rm api python3 manage.py createsuperuser`

### What did you expect to happen?

I expected an interactive input to provide email and password for new superuser account.

The create account should have all permissions in Saleor, just like `populatedb --createsuperuser` admin account.

### Logs

```
2023-01-18 14:30:19,333 WARNING saleor.core.jwt_manager RSA_PRIVATE_KEY is missing. Using temporary key for local development with DEBUG mode. [PID:1:MainThread]
Traceback (most recent call last):
  File "/app/manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/base.py", line 354, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.9/site-packages/django/contrib/auth/management/commands/createsuperuser.py", line 79, in execute
    return super().execute(*args, **options)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.9/site-packages/django/contrib/auth/management/commands/createsuperuser.py", line 100, in handle
    default_username = get_default_username(database=database)
  File "/usr/local/lib/python3.9/site-packages/django/contrib/auth/management/__init__.py", line 115, in get_default_username
    from django.contrib.auth import models as auth_app
  File "/usr/local/lib/python3.9/site-packages/django/contrib/auth/models.py", line 35, in <module>
    class Permission(models.Model):
  File "/usr/local/lib/python3.9/site-packages/django/db/models/base.py", line 113, in __new__
    raise RuntimeError(
RuntimeError: Model class django.contrib.auth.models.Permission doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.
ERROR: 1
```

### Environment

- Saleor version: latest (`4d5b30419f7a7bb9aa572eb2e085ab7277a68601`)
- OS and version: macOS 13.0.1
- Docker version 20.10.21, build baeda1f
- docker-compose version 1.29.2, build 5becea4c
- Saleor platform version: latest (`4d5b30419f7a7bb9aa572eb2e085ab7277a68601`)


Discussion:
I was having the same issue. 

I used the command found [here](https://github.com/saleor/saleor-platform/blob/main/README.md#troubleshooting:~:text=Populate%20the%20database%20with%20example%20data%20and%20create%20the%20admin%20user%3A) instead, and did not return an error

`docker-compose run --rm api python3 manage.py populatedb --createsuperuser`

This allowed me to use admin@example.com with the password admin





Resolving patch:
diff --git a/saleor/account/management/commands/createsuperuser.py b/saleor/account/management/commands/createsuperuser.py
--- a/saleor/account/management/commands/createsuperuser.py
+++ b/saleor/account/management/commands/createsuperuser.py
@@ -1,3 +1,293 @@
-from django.contrib.auth.management.commands.createsuperuser import Command
+# mypy: ignore-errors
+"""Management utility to create superusers.
 
-__all__ = ["Command"]
+This module was moved for Django Auth directly to Saleor.
+"""
+import getpass
+import os
+import sys
+
+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.text import capfirst
+
+
+class NotRunningInTTYException(Exception):
+    pass
+
+
+PASSWORD_FIELD = "password"
+
+
+class Command(BaseCommand):
+    help = "Used to create a superuser."
+    requires_migrations_checks = True
+    stealth_options = ("stdin",)
+
+    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(
+            "--%s" % self.UserModel.USERNAME_FIELD,
+            help="Specifies the login for the superuser.",
+        )
+        parser.add_argument(
+            "--noinput",
+            "--no-input",
+            action="store_false",
+            dest="interactive",
+            help=(
+                "Tells Django to NOT prompt the user for input of any kind. "
+                "You must use --%s with --noinput, along with an option for "
+                "any other required field. Superusers created with --noinput will "
+                "not be able to log in until they're given a valid password."
+                % self.UserModel.USERNAME_FIELD
+            ),
+        )
+        parser.add_argument(
+            "--database",
+            default=DEFAULT_DB_ALIAS,
+            help='Specifies the database to use. Default is "default".',
+        )
+        for field_name in self.UserModel.REQUIRED_FIELDS:
+            field = self.UserModel._meta.get_field(field_name)
+            if field.many_to_many:
+                if (
+                    field.remote_field.through
+                    and not field.remote_field.through._meta.auto_created
+                ):
+                    raise CommandError(
+                        "Required field '%s' specifies a many-to-many "
+                        "relation through model, which is not supported." % field_name
+                    )
+                else:
+                    parser.add_argument(
+                        "--%s" % field_name,
+                        action="append",
+                        help=(
+                            f"Specifies the {field_name} for the superuser. Can be "
+                            "used multiple times."
+                        ),
+                    )
+            else:
+                parser.add_argument(
+                    "--%s" % field_name,
+                    help="Specifies the %s for the superuser." % field_name,
+                )
+
+    def execute(self, *args, **options):
+        self.stdin = options.get("stdin", sys.stdin)  # Used for testing
+        return super().execute(*args, **options)
+
+    def handle(self, *args, **options):
+        username = options[self.UserModel.USERNAME_FIELD]
+        database = options["database"]
+        user_data = {}
+        verbose_field_name = self.username_field.verbose_name
+        try:
+            self.UserModel._meta.get_field(PASSWORD_FIELD)
+        except exceptions.FieldDoesNotExist:
+            pass
+        else:
+            # If not provided, create the user with an unusable password.
+            user_data[PASSWORD_FIELD] = None
+        try:
+            if options["interactive"]:
+                # Same as user_data but without many to many fields and with
+                # foreign keys as fake model instances instead of raw IDs.
+                fake_user_data = {}
+                if hasattr(self.stdin, "isatty") and not self.stdin.isatty():
+                    raise NotRunningInTTYException
+                default_username = ""
+                if username:
+                    error_msg = self._validate_username(
+                        username, verbose_field_name, database
+                    )
+                    if error_msg:
+                        self.stderr.write(error_msg)
+                        username = None
+                elif username == "":
+                    raise CommandError(
+                        "%s cannot be blank." % capfirst(verbose_field_name)
+                    )
+                # Prompt for username.
+                while username is None:
+                    message = self._get_input_message(
+                        self.username_field, default_username
+                    )
+                    username = self.get_input_data(
+                        self.username_field, message, default_username
+                    )
+                    if username:
+                        error_msg = self._validate_username(
+                            username, verbose_field_name, database
+                        )
+                        if error_msg:
+                            self.stderr.write(error_msg)
+                            username = None
+                            continue
+                user_data[self.UserModel.USERNAME_FIELD] = username
+                fake_user_data[self.UserModel.USERNAME_FIELD] = (
+                    self.username_field.remote_field.model(username)
+                    if self.username_field.remote_field
+                    else username
+                )
+                # Prompt for required fields.
+                for field_name in self.UserModel.REQUIRED_FIELDS:
+                    field = self.UserModel._meta.get_field(field_name)
+                    user_data[field_name] = options[field_name]
+                    while user_data[field_name] is None:
+                        message = self._get_input_message(field)
+                        input_value = self.get_input_data(field, message)
+                        user_data[field_name] = input_value
+                        if field.many_to_many and input_value:
+                            if not input_value.strip():
+                                user_data[field_name] = None
+                                self.stderr.write("Error: This field cannot be blank.")
+                                continue
+                            user_data[field_name] = [
+                                pk.strip() for pk in input_value.split(",")
+                            ]
+                        if not field.many_to_many:
+                            fake_user_data[field_name] = input_value
+
+                        # Wrap any foreign keys in fake model instances
+                        if field.many_to_one:
+                            fake_user_data[field_name] = field.remote_field.model(
+                                input_value
+                            )
+
+                # Prompt for a password if the model has one.
+                while PASSWORD_FIELD in user_data and user_data[PASSWORD_FIELD] is None:
+                    password = getpass.getpass()
+                    password2 = getpass.getpass("Password (again): ")
+                    if password != password2:
+                        self.stderr.write("Error: Your passwords didn't match.")
+                        # Don't validate passwords that don't match.
+                        continue
+                    if password.strip() == "":
+                        self.stderr.write("Error: Blank passwords aren't allowed.")
+                        # Don't validate blank passwords.
+                        continue
+                    try:
+                        validate_password(password2, self.UserModel(**fake_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":
+                            continue
+                    user_data[PASSWORD_FIELD] = password
+            else:
+                # Non-interactive mode.
+                # Use password from environment variable, if provided.
+                if (
+                    PASSWORD_FIELD in user_data
+                    and "DJANGO_SUPERUSER_PASSWORD" in os.environ
+                ):
+                    user_data[PASSWORD_FIELD] = os.environ["DJANGO_SUPERUSER_PASSWORD"]
+                # Use username from environment variable, if not provided in
+                # options.
+                if username is None:
+                    username = os.environ.get(
+                        "DJANGO_SUPERUSER_" + self.UserModel.USERNAME_FIELD.upper()
+                    )
+                if username is None:
+                    raise CommandError(
+                        "You must use --%s with --noinput."
+                        % self.UserModel.USERNAME_FIELD
+                    )
+                else:
+                    error_msg = self._validate_username(
+                        username, verbose_field_name, database
+                    )
+                    if error_msg:
+                        raise CommandError(error_msg)
+
+                user_data[self.UserModel.USERNAME_FIELD] = username
+                for field_name in self.UserModel.REQUIRED_FIELDS:
+                    env_var = "DJANGO_SUPERUSER_" + field_name.upper()
+                    value = options[field_name] or os.environ.get(env_var)
+                    if not value:
+                        raise CommandError(
+                            "You must use --%s with --noinput." % field_name
+                        )
+                    field = self.UserModel._meta.get_field(field_name)
+                    user_data[field_name] = field.clean(value, None)
+
+            self.UserModel._default_manager.db_manager(database).create_superuser(
+                **user_data
+            )
+            if options["verbosity"] >= 1:
+                self.stdout.write("Superuser 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(
+                "Superuser creation skipped due to not running in a TTY. "
+                "You can run `manage.py createsuperuser` in your project "
+                "to create one manually."
+            )
+
+    def get_input_data(
+        self, field, message, default=None
+    ):  # noqa: D205, D212, D400, D415
+        """
+        Override this method if you want to customize data inputs or
+        validation exceptions.
+        """
+        raw_value = input(message)
+        if default and raw_value == "":
+            raw_value = default
+        try:
+            val = field.clean(raw_value, None)
+        except exceptions.ValidationError as e:
+            self.stderr.write("Error: %s" % "; ".join(e.messages))
+            val = None
+
+        return val
+
+    def _get_input_message(self, field, default=None):
+        return "%s%s%s: " % (
+            capfirst(field.verbose_name),
+            " (leave blank to use '%s')" % default if default else "",
+            " (%s.%s)"
+            % (
+                field.remote_field.model._meta.object_name,
+                field.m2m_target_field_name()
+                if field.many_to_many
+                else field.remote_field.field_name,
+            )
+            if field.remote_field
+            else "",
+        )
+
+    def _validate_username(self, username, verbose_field_name, database):
+        """Validate username. If invalid, return a string error message."""
+        if self.username_field.unique:
+            try:
+                self.UserModel._default_manager.db_manager(database).get_by_natural_key(
+                    username
+                )
+            except self.UserModel.DoesNotExist:
+                pass
+            else:
+                return "Error: That %s is already taken." % verbose_field_name
+        if not username:
+            return "%s cannot be blank." % capfirst(verbose_field_name)
+        try:
+            self.username_field.clean(username, None)
+        except exceptions.ValidationError as e:
+            return "; ".join(e.messages)
diff --git a/saleor/account/models.py b/saleor/account/models.py
--- a/saleor/account/models.py
+++ b/saleor/account/models.py
@@ -144,9 +144,14 @@ def create_user(
         return user
 
     def create_superuser(self, email, password=None, **extra_fields):
-        return self.create_user(
+        user = self.create_user(
             email, password, is_staff=True, is_superuser=True, **extra_fields
         )
+        group, created = Group.objects.get_or_create(name="Full Access")
+        if created:
+            group.permissions.add(*get_permissions())
+        group.user_set.add(user)
+        return user
 
     def customers(self):
         orders = Order.objects.values("user_id")


Test patch:
diff --git a/saleor/account/tests/test_account_command.py b/saleor/account/tests/test_account_command.py
new file mode 100644
--- /dev/null
+++ b/saleor/account/tests/test_account_command.py
@@ -0,0 +1,53 @@
+import os
+from unittest import mock
+
+from django.core.management import call_command
+
+from ..models import Group, User
+
+
+def test_createsuperuser_command(db):
+    # given
+
+    # when
+    call_command("createsuperuser", email="testAdmin@example.com", interactive=False)
+
+    # then
+    assert User.objects.count() == 1
+    assert User.objects.get(
+        email="testAdmin@example.com", is_staff=True, is_superuser=True
+    )
+    assert Group.objects.count() == 1
+    assert Group.objects.get(name="Full Access")
+
+
+def test_createsuperuser_command_dont_override_group(db):
+    # given
+    group = Group.objects.create(name="Full Access")
+
+    # when
+    call_command("createsuperuser", email="testAdmin@example.com", interactive=False)
+
+    # then
+    assert User.objects.count() == 1
+    assert User.objects.get(
+        email="testAdmin@example.com", is_staff=True, is_superuser=True
+    )
+    group.refresh_from_db()
+    assert not group.permissions.all()
+
+
+@mock.patch.dict(os.environ, {"DJANGO_SUPERUSER_EMAIL": "adminTest@example.com"})
+def test_createsuperuser_command_email_from_settings(db):
+    # given
+
+    # when
+    call_command("createsuperuser", interactive=False)
+
+    # then
+    assert User.objects.count() == 1
+    assert User.objects.get(
+        email="adminTest@example.com", is_staff=True, is_superuser=True
+    )
+    assert Group.objects.count() == 1
+    assert Group.objects.get(name="Full Access")
diff --git a/saleor/graphql/tests/fixtures.py b/saleor/graphql/tests/fixtures.py
--- a/saleor/graphql/tests/fixtures.py
+++ b/saleor/graphql/tests/fixtures.py
@@ -197,7 +197,13 @@ def graphql_log_handler():
 
 @pytest.fixture
 def superuser():
-    superuser = User.objects.create_superuser("superuser@example.com", "pass")
+    superuser = User.objects.create_user(
+        "superuser@example.com",
+        "pass",
+        is_staff=True,
+        is_active=True,
+        is_superuser=True,
+    )
     return superuser
 
 
diff --git a/saleor/tests/fixtures.py b/saleor/tests/fixtures.py
--- a/saleor/tests/fixtures.py
+++ b/saleor/tests/fixtures.py
@@ -1056,7 +1056,13 @@ def order_unconfirmed(order):
 @pytest.fixture
 def admin_user(db):
     """Return a Django admin user."""
-    return User.objects.create_superuser("admin@example.com", "password")
+    return User.objects.create_user(
+        "admin@example.com",
+        "password",
+        is_staff=True,
+        is_active=True,
+        is_superuser=True,
+    )
 
 
 @pytest.fixture