nrbnlulu / strawberry-django-auth

Authentication system for django using strawberry
https://nrbnlulu.github.io/strawberry-django-auth/
MIT License
59 stars 27 forks source link

[Help Needed] TypeError: UserType fields cannot be resolved on a custom user model #532

Open ash-lyrid opened 2 months ago

ash-lyrid commented 2 months ago

I am trying to use this package, coming from django graphene auth background.

I am getting the following error - TypeError: UserType fields cannot be resolved. GraphQL type for model field 'users.User.email' has not been implemented

GqlAuthSettings

    LOGIN_REQUIRE_CAPTCHA=False,
    REGISTER_REQUIRE_CAPTCHA=False,
    ALLOW_LOGIN_NOT_VERIFIED=True,
    LOGIN_FIELDS={email_field, password_field},
    REGISTER_MUTATION_FIELDS={email_field},

CustomUserModel

class User(AbstractBaseUser, PermissionsMixin):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    email = LowercaseEmailField(
        verbose_name=_("email address"), unique=True,
        error_messages={
            'unique': _(
                "A user is already registered with this email address"),
        },
    )
    first_name = models.CharField(_('first name'), max_length=150)
    middle_name = models.CharField(_('middle name'), max_length=150, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)
    is_staff = models.BooleanField(
        verbose_name=_("staff status"),
        default=False,
        help_text=_(
            "Designates whether the user can log into this admin site."
        ),
    )
    is_active = models.BooleanField(
        verbose_name=_("active"),
        default=True,
        help_text=_(
            "Designates whether this user should be treated as active. "
            "Unselect this instead of deleting accounts."
        ),
    )
    date_joined = models.DateTimeField(verbose_name=_("date joined"), default=timezone.now)
    mobile_number = PhoneNumberField(blank=True)
    modified = models.DateTimeField(auto_now=True)

schema.py

from typing import AsyncGenerator

import strawberry
import strawberry_django
from strawberry.types import Info
from strawberry_django.permissions import IsAuthenticated

from gqlauth.user import arg_mutations
from gqlauth.core.utils import get_user
from gqlauth.user.resolvers import Captcha
from gqlauth.user.queries import UserQueries
from gqlauth.core.middlewares import JwtSchema

@strawberry.type
class Query(UserQueries):
    @strawberry_django.field(
        directives=[
            IsAuthenticated(),
        ]
    )
    def whatsMyUserName(self, info: Info) -> str:
        return get_user(info).username

    @strawberry.field()
    def amIAnonymous(self, info: Info) -> bool:
        user = get_user(info)
        return not user.is_authenticated

@strawberry.type
class Mutation:

    verify_token = arg_mutations.VerifyToken.field
    update_account = arg_mutations.UpdateAccount.field
    archive_account = arg_mutations.ArchiveAccount.field
    delete_account = arg_mutations.DeleteAccount.field
    password_change = arg_mutations.PasswordChange.field

    captcha = Captcha.field
    token_auth = arg_mutations.ObtainJSONWebToken.field
    register = arg_mutations.Register.field
    verify_account = arg_mutations.VerifyAccount.field
    resend_activation_email = arg_mutations.ResendActivationEmail.field
    send_password_reset_email = arg_mutations.SendPasswordResetEmail.field
    password_reset = arg_mutations.PasswordReset.field
    password_set = arg_mutations.PasswordSet.field
    refresh_token = arg_mutations.RefreshToken.field
    revoke_token = arg_mutations.RevokeToken.field

@strawberry.type
class Subscription:
    @strawberry.subscription()
    async def whatsMyName(self, info: Info, target: int = 10) -> AsyncGenerator[str, None]:
        user = get_user(info)
        assert user.is_authenticated
        for _ in range(target):
            yield get_user(info).username

schema = JwtSchema(query=Query, mutation=Mutation, subscription=Subscription)
ash-lyrid commented 2 months ago

I think there is no straight forward way to add the email type to the user type as I am using a custom email field. I can see there is a TODO item to support custom UserType implementation. I will wait till that is implemented as I am unable to use this package for the time being. Please correct me if there is an alternative way to resolve this. Thanks.

nrbnlulu commented 2 months ago

what's your email_field?

ash-lyrid commented 2 months ago

from gqlauth.settings_type import GqlAuthSettings, password_field, email_field I am using email_field from gqlauth only

This is my custom email field.

class LowercaseEmailField(models.EmailField):
    """
    Override EmailField to convert emails to lowercase before saving.
    """
    def to_python(self, value):
        """
        Convert email to lowercase.
        """
        value = super(LowercaseEmailField, self).to_python(value)
        # Value can be None so check that it's a string before lowercasing.
        if isinstance(value, str):
            return value.lower()
        return value
nrbnlulu commented 2 months ago

Can you show your GQLAUTH_SETTINGS? including the implementations of email_field etc and a full stacktrace

ash-lyrid commented 2 months ago
GQL_AUTH = GqlAuthSettings(
    LOGIN_REQUIRE_CAPTCHA=False,
    REGISTER_REQUIRE_CAPTCHA=False,
    ALLOW_LOGIN_NOT_VERIFIED=True,
    LOGIN_FIELDS={email_field, password_field},
    REGISTER_MUTATION_FIELDS={email_field},
    # CAPTCHA_EXPIRATION_DELTA=timedelta(seconds=120),
    # CAPTCHA_MAX_RETRIES=5,
    # CAPTCHA_TEXT_FACTORY=default_text_factory,
    # CAPTCHA_TEXT_VALIDATOR=default_captcha_text_validator,
    # FORCE_SHOW_CAPTCHA=False,
    # CAPTCHA_SAVE_IMAGE=False,
    UPDATE_MUTATION_FIELDS={email_field},
    JWT_PAYLOAD_PK=email_field,
)

Traceback (most recent call last):
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/graphql/type/definition.py", line 808, in fields
    fields = resolve_thunk(self._fields)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/graphql/type/definition.py", line 300, in resolve_thunk
    return thunk() if callable(thunk) else thunk
           ^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/strawberry/schema/schema_converter.py", line 525, in <lambda>
    fields=lambda: self.get_graphql_fields(object_type),
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/strawberry/schema/schema_converter.py", line 382, in get_graphql_fields
    return _get_thunk_mapping(
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/strawberry/schema/schema_converter.py", line 129, in _get_thunk_mapping
    field_type = field.type
                 ^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/strawberry/field.py", line 302, in type
    return self.resolve_type()
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/strawberry_django/fields/base.py", line 184, in resolve_type
    resolved_type = resolve_model_field_type(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/strawberry_django/fields/types.py", line 508, in resolve_model_field_type
    raise NotImplementedError(
NotImplementedError: GraphQL type for model field 'users.User.email' has not been implemented

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/ashish/Projects/Lyrid/FreeI/manage.py", line 24, in <module>
    main()
  File "/Users/ashish/Projects/Lyrid/FreeI/manage.py", line 19, in main
    execute_from_command_line(sys.argv)
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
    utility.execute()
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/core/management/__init__.py", line 436, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/core/management/base.py", line 412, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/contrib/auth/management/commands/createsuperuser.py", line 88, in execute
    return super().execute(*args, **options)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/core/management/base.py", line 453, in execute
    self.check()
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/core/management/base.py", line 485, in check
    all_issues = checks.run_checks(
                 ^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/core/checks/registry.py", line 88, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/core/checks/urls.py", line 14, in check_url_config
    return check_resolver(resolver)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/core/checks/urls.py", line 24, in check_resolver
    return check_method()
           ^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/urls/resolvers.py", line 494, in check
    for pattern in self.url_patterns:
                   ^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/urls/resolvers.py", line 715, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
                       ^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/django/urls/resolvers.py", line 708, in urlconf_module
    return import_module(self.urlconf_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/ashish/Projects/Lyrid/FreeI/config/urls.py", line 6, in <module>
    from .schema import schema
  File "/Users/ashish/Projects/Lyrid/FreeI/config/schema.py", line 64, in <module>
    schema = JwtSchema(
             ^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/strawberry/schema/schema.py", line 143, in __init__
    self._schema = GraphQLSchema(
                   ^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/graphql/type/schema.py", line 224, in __init__
    collect_referenced_types(query)
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/graphql/type/schema.py", line 433, in collect_referenced_types
    collect_referenced_types(field.type)
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/graphql/type/schema.py", line 432, in collect_referenced_types
    for field in named_type.fields.values():
                 ^^^^^^^^^^^^^^^^^
  File "/usr/local/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/functools.py", line 1001, in __get__
    val = self.func(instance)
          ^^^^^^^^^^^^^^^^^^^
  File "/Users/ashish/Env/FreeI/lib/python3.11/site-packages/graphql/type/definition.py", line 811, in fields
    raise cls(f"{self.name} fields cannot be resolved. {error}") from error
TypeError: UserType fields cannot be resolved. GraphQL type for model field 'users.User.email' has not been implemented
nrbnlulu commented 2 months ago

Dude where do you get email_field from ?

ash-lyrid commented 2 months ago

Using the following. from gqlauth.settings_type import GqlAuthSettings, password_field, email_field

nrbnlulu commented 2 months ago

I see, thats weird. could you create a reproducible example?