The view has IsAdminUser restriction. I am also using token authentication.
This is the view:
class UserRegister(generics.CreateAPIView):
permission_classes = [IsAdminUser]
queryset = User.objects.all()
serializer_class = RegisterSerializer
This are the Model and ModelManager:
class UserManager(BaseUserManager):
def create_superuser(
self,
name: str,
email: str,
number: str,
password: Optional[str] = None,
) -> "User":
return self.create_user(name, email, number, password, True, True)
def create_staff(
self,
name: str,
email: str,
number: str,
password: Optional[str] = None,
) -> "User":
return self.create_user(name, email, number, password, True)
def create_user(
self,
name: str,
email: str,
number: str,
password: Optional[str] = None,
is_staff: bool = False,
is_superuser: bool = False,
) -> "User":
user = User(
name=name,
email=self.normalize_email(email),
number=number,
is_staff=is_staff,
is_superuser=is_superuser,
)
user.set_password(password)
user.save(using=self._db)
token = Token.objects.create(user=user)
return user
class User(AbstractBaseUser, PermissionsMixin):
name = models.CharField(max_length=50, unique=True)
email = models.EmailField(max_length=250, unique=True)
date = models.DateTimeField("Date", auto_now=True)
number = models.CharField(max_length=100, default="")
is_superuser = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
objects = UserManager()
USERNAME_FIELD = "name"
REQUIRED_FIELDS = ["email", "number", "password"]
def set_password(self, password: Optional[str] = None) -> Any:
if password:
self.password = password
else:
self.password = ""
return self.password
def __str__(self) -> str:
return f"{self.name} ({self.email}) {self.number} {self.password}"
This is the serializer I am using:
class RegisterSerializer(serializers.ModelSerializer):
repeat_password = serializers.CharField(
write_only=True, style={"input_type": "password"}
)
class Meta:
model = User
fields = ("name", "email", "number", "password", "repeat_password", "is_staff")
extra_kwargs = {"password": {"write_only": True}}
def validate(self, attrs: dict) -> dict:
password = attrs.get("password")
repeat_password = attrs.get("repeat_password")
if password != repeat_password:
raise serializers.ValidationError("Passwords do not match.")
return attrs
def create(self, validated_password: Any) -> Any:
repeat_password = validated_password.pop("repeat_password")
return User.objects.create_user(**validated_password)
And this is the tests code:
class TestSetUp(APITestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user_manager = UserManager()
def setUp(self):
self.register_url = reverse("user_urls:register")
self.register_user_data = {
"name": "TestUser",
"email": "[email protected]",
"number": "1213",
"password": "Test@123",
}
self.login_url = reverse("user_urls:login")
self.login_user_data = {
"name": "TestUser",
"password": "Test@123",
}
self.register_user_data_with_empty_password = {
"name": "TestUser",
"email": "[email protected]",
"number": "1213",
"password": "",
}
return super().setUp()
def create_staff(self):
self.user = self.user_manager.create_staff(**self.register_user_data)
try:
self.token = Token.objects.get(user=self.user)
except Token.DoesNotExist:
self.token = Token.objects.create(user=self.user)
def create_superuser(self):
self.user = self.user_manager.create_superuser(**self.register_user_data)
try:
self.token = Token.objects.get(user=self.user)
except Token.DoesNotExist:
self.token = Token.objects.create(user=self.user)
This snippet is supposed create a user using the custom built model User and UserManager, with only the fields specified in the code(name, email, number, password)
class TestViews(TestSetUp):
def test_register_with_no_data(self) -> None:
res = self.client.post(self.register_url)
self.assertEqual(res.status_code, 401)
def test_register_with_data(self) -> None:
User.objects.create_staff(**self.register_user_data)
res = self.client.post(self.register_url)
self.assertEqual(res.status_code, 201)
def test_user_cannot_login_with_invalid_credentials(self):
self.client.post(self.register_url,self.register_user_data, format='json')
res = self.client.post(
self.login_url, self.login_user_data, format="json")
self.assertEqual(res.status_code, 401)
def test_user_can_login_with_valid_credentials(self):
res = self.client.post(
self.register_url, self.login_user_data, format='json'
)
if res.status_code != 201:
raise ValueError(f"Registration failed with error: {res.data}")
name = res.data["name"]
password = res.data["password"]
user = User.objects.get(name=name, password=password)
user.is_verified = True
user.save()
response = self.client.post(
self.login_url, self.login_user_data, format="json")
self.assertEqual(response.status_code, 201)
Here I need the TestSetUp to be called and for it to create a normal user, this is restricted by the IsAdminUser restriction in the view, meaning only the Authentication Token of a staff or superuser is allowed to create a new normal user.
Every time I run coverage I get a 401 Error with the message
ValueError: Registration failed with error: {'detail': ErrorDetail(string='Authentication credentials were not provided.', code='not_authenticated')}
This is the traceback log of the error:
reating test database for alias 'default'...
System check identified no issues (0 silenced).
..F.EF
======================================================================
ERROR: test_user_can_login_with_valid_credentials (user.tests.tests_views.TestViews.test_user_can_login_with_valid_credentials)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\Owner\Desktop\myapp\user\tests\tests_views.py", line 23, in test_user_can_login_with_valid_credentials
raise ValueError(f"Registration failed with error: {res.data}")
ValueError: Registration failed with error: {'detail': ErrorDetail(string='Authentication credentials were not provided.', code='not_authenticated')}
======================================================================
FAIL: test_register_with_data (user.tests.tests_views.TestViews.test_register_with_data)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\Owner\Desktop\myapp\user\tests\tests_views.py", line 13, in test_register_with_data
self.assertEqual(res.status_code, 201)
AssertionError: 401 != 201
======================================================================
FAIL: test_user_cannot_login_with_invalid_credentials (user.tests.tests_views.TestViews.test_user_cannot_login_with_invalid_credentials)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\Owner\Desktop\myapp\user\tests\tests_views.py", line 18, in test_user_cannot_login_with_invalid_credentials
self.assertEqual(res.status_code, 401)
AssertionError: 400 != 401
----------------------------------------------------------------------
Ran 6 tests in 0.045s
FAILED (failures=2, errors=1)
Destroying test database for alias 'default'...
I haven't tried much because I don't understand what I'm supposed to do.
I have looked through the DRF Test docs and I can't find anything that could help me deal with this, can anybody help?
Also this is my first time asking a question in StackOverflow, so if I am unclear about anything please tell me.
I wasn't sure if the urls are in any way necessary to this, so I decided to add the at the bottom:
app_name = "user_urls"
router = routers.SimpleRouter()
router.register(r"user", UserView)
urlpatterns = [
path(
"register/",
UserRegister.as_view(),
name="register",
),
path("login/", obtain_auth_token, name="login"),
path(
"logout/",
auth_views.LogoutView.as_view(),
name="logout",
),
]
urlpatterns += router.urls
https://www.django-rest-framework.org/api-guide/testing/ please have a look testing docs of DRF, you normally create a user and it will create easily, but when you create a user and access to some API's you have to authenticate that use like this as DRF testing tells
self.client.force_authenticate(user=the user you created will come here)
after that, you can get or post on that API