django factory using a factory gives "Database access not allowed" - django

I want to create a user_profile from a factory called UserProfileFactory which uses a User object from UserFactory.
the error is: RuntimeError: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.
here are the relivant classes.
from django.contrib.auth import get_user_model
from factory import Faker, post_generation
from factory.django import DjangoModelFactory
class UserFactory(DjangoModelFactory):
username = Faker("user_name")
email = Faker("email")
name = Faker("name")
#post_generation
def password(self, create: bool, extracted: Sequence[Any], **kwargs):
password = (
extracted
if extracted
else Faker(
"password",
length=42,
special_chars=True,
digits=True,
upper_case=True,
lower_case=True,
).evaluate(None, None, extra={"locale": None})
)
self.set_password(password)
class Meta:
model = get_user_model()
django_get_or_create = ["username"]
class UserProfileFactory(DjangoModelFactory):
user = UserFactory.create() #### the problem line ###
country = Faker("country") # which laws apply
birth_date = Faker("date_of_birth") # in the US you can't collect data from <13yo's
class Meta:
model = UserProfile
and in the tests/test.py
class TestUserProfileDetailView():
def test_create_userprofile(self):
"""creates an APIRequest and uses an instance of UserProfile to test a view user_detail_view"""
factory = APIRequestFactory()
request = factory.get('/api/userprofile/')
request.user = UserProfileFactory.create() # problem starts here #
response = user_detail_view(request)
self.assertEqual(response.status_code, 200)

(I know this is an old one, but in case someone else finds this question later)
I think what you want is the build method. The create method saves the instance in the database (which you said you don't want) but the build method doesn't.
Here's an explanation in the factory-boy docs of build and create.

Related

Django test view fails: (1) unable to logged in a new created user and (2) CBV create view test do not create object in test database

I have a Django project and I want to implent unit tests.
I want to test a class based view (CBV) name PatientCreate that need to be authentificated.
It is important to not that the sqlite test dabase is already populated with users (data migration).
In my PatientTestCase class, I start defining setup where is created a new superuser named 'test' and logged in the new created user.
Then, I test if user 'test' is logged in but test fails. If logged in a user already registered in test database (i.e. user named 'admin'), test success.
To test for PatientCreate CBV, I write a post request and test if number of patients increase by one. But this test fails, even if I logged in with 'admin'. It seems that patient is not created in test database.
Note that, response.status_code = 200.
I can not figure out where comes my issue.
project architecture
- core
- urls.py
- ecrf
- urls.py
- views.py
tests.py
class PatientTestCase(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_superuser(
username='test', password='test', email='test#test.fr')
# => print(self.user) return test so user is created
self.login_success = self.client.login(username='test', password='test')
def test_new_patient_is_created(self):
self.assertTrue(self.login_success) # => return False with 'test' but will success with 'admin'
patient_site = Site.objects.get(sit_ide=4)
number_of_patients_before = Patient.objects.count()
response = self.client.post(reverse('ecrf:patient_create'), {
'pat': 'TES001',
'pat_sit': patient_site
}, follow=True, HTTP_ACCEPT_LANGUAGE='fr')
number_of_patients_after = Patient.objects.count()
self.assertEqual(number_of_patients_after,
number_of_patients_before + 1) # => return False
ecrf/views
#method_decorator(login_required, name="dispatch")
class PatientCreate(SuccessMessageMixin, CreateView):
model = Patient
form_class = PatientForm
success_message = "Le patient a été créé."
def get_success_url(self):
return reverse("ecrf:index")
def form_valid(self, form):
form.instance.pat_sai_log = self.request.user.username
form.save()
return super().form_valid(form)
In fact, I realized that CBV testing is obviously different than FBV.
Below, my solution, hope this help and is correct:
class CBVCreateViewTestCase(TestCase):
def setUp(self):
self.user = User.objects.create_superuser(username='test', password='test', email='test#test.fr')
self.data= {...}
def test_object_is_created(self):
condition_before = ...
request = RequestFactory().post(reverse('app_name:view_name'), self.data,
follow=True, HTTP_ACCEPT_LANGUAGE='fr')
# logged-in user
request.user = self.user
# call to CBV
response = CBVCreateView.as_view()(request)
condition_after = ...
# Test condition_before and condition_after
Dealing with logged-in problem with FBV is that creating superuser using create_superuser() function lead to password not being properly hashed in unitest and I do not know why.
But, testing password of user created in setup method with check_password() method fails. But if I create user using create() method, and set user password 'manually' using set_password() method, it works!

Obtaining data from user

How do I obtain the field data from a User auth query within a class View. It's on the django 2.2.x framework using Python3
This code works just fine...
from django.contrib.auth.models import User
class PaymentsReportView(ListView):
template_name = 'payments/Payment_list.html'
userid = 'James'
queryset = Payment.objects.filter(Landlord_FK__User_id=userid)
but this doesn't...
class PaymentsReportView(ListView):
template_name = 'payments/Payment_list.html'
userid = User.username # where the username is James
queryset = Payment.objects.filter(Landlord_FK__User_id=userid)
How can I check the output of User.username to see if it's valid? What am I missing to get the data? The code doaen't break. It just returns empty.
You can't do that at class level. What you need to do is to define a get_queryset method and do the filtering there:
class PaymentsReportView(ListView):
template_name = 'payments/Payment_list.html'
def get_queryset(self):
userid = self.request.user.username
return Payment.objects.filter(Landlord_FK__User_id=userid)
Although I must note that this implementation is odd; why is the userid the username, rather than the ID? Usually I would expect the filter to be (Landlord_FK=request.user). You should show your models.

Delete mutation in Django GraphQL

The docs of Graphene-Django pretty much explains how to create and update an object. But how to delete it? I can imagine the query to look like
mutation mut{
deleteUser(id: 1){
user{
username
email
}
error
}
}
but i doubt that the correct approach is to write the backend code from scratch.
Something like this, where UsersMutations is part of your schema:
class DeleteUser(graphene.Mutation):
ok = graphene.Boolean()
class Arguments:
id = graphene.ID()
#classmethod
def mutate(cls, root, info, **kwargs):
obj = User.objects.get(pk=kwargs["id"])
obj.delete()
return cls(ok=True)
class UserMutations(object):
delete_user = DeleteUser.Field()
Here is a small model mutation you can add to your project based on the relay ClientIDMutation and graphene-django's SerializerMutation. I feel like this or something like this should be part of graphene-django.
import graphene
from graphene import relay
from graphql_relay.node.node import from_global_id
from graphene_django.rest_framework.mutation import SerializerMutationOptions
class RelayClientIdDeleteMutation(relay.ClientIDMutation):
id = graphene.ID()
message = graphene.String()
class Meta:
abstract = True
#classmethod
def __init_subclass_with_meta__(
cls,
model_class=None,
**options
):
_meta = SerializerMutationOptions(cls)
_meta.model_class = model_class
super(RelayClientIdDeleteMutation, cls).__init_subclass_with_meta__(
_meta=_meta, **options
)
#classmethod
def get_queryset(cls, queryset, info):
return queryset
#classmethod
def mutate_and_get_payload(cls, root, info, client_mutation_id):
id = int(from_global_id(client_mutation_id)[1])
cls.get_queryset(cls._meta.model_class.objects.all(),
info).get(id=id).delete()
return cls(id=client_mutation_id, message='deleted')
Use
class DeleteSomethingMutation(RelayClientIdDeleteMutation):
class Meta:
model_class = SomethingModel
You can also override get_queryset.
I made this library for simple model mutations: https://github.com/topletal/django-model-mutations , you can see how to delete user(s) in examples there
class UserDeleteMutation(mutations.DeleteModelMutation):
class Meta:
model = User
Going by the Python + Graphene hackernews tutorial, I derived the following implementation for deleting a Link object:
class DeleteLink(graphene.Mutation):
# Return Values
id = graphene.Int()
url = graphene.String()
description = graphene.String()
class Arguments:
id = graphene.Int()
def mutate(self, info, id):
link = Link.objects.get(id=id)
print("DEBUG: ${link.id}:${link.description}:${link.url}")
link.delete()
return DeleteLink(
id=id, # Strangely using link.id here does yield the correct id
url=link.url,
description=link.description,
)
class Mutation(graphene.ObjectType):
create_link = CreateLink.Field()
delete_link = DeleteLink.Field()

IntegrityError when using .save() on OneToOneField in Django

I have 2 models
class User(models.Model):
email = models.CharField(max_length = 100)
password = models.CharField(max_length = 100)
class Authentication(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE)
token = models.CharField(max_length =50, null = True)
Here is the code I use for login in views.py
from user.models import User, Authentication
import uuid
from django.db.utils import IntegrityError
user = User.objects.get(pk = request.user.id)
token = uuid.uuid4().hex
try:
Authentication.objects.create(user = user, token = token)
except IntegrityError:
user.authentication.token = token
user.authentication.save()
return JsonResponse({'token':token})
The problem I meet here is everytime a user login, I will generate a new token and save in the database. The error appears at the line
user.authentication.save()
The error is: django.db.utils.IntegrityError: (1062, "Duplicate entry '30' for key 'user_id' ")
The 30 is the user_id existed in Authentication models when login in the second time ( the first time login is always succeed).
I solved this problem by using these codes:
except IntegrityError:
au = Authentication.objects.get(user = user)
au.token = token
au.save()
But I dont know why i got this bug. I used to do the first approach many times but not got no bugs at all. Anyone has faced to this problem can show me the way to solve this? Thank you
The problem is that you are trying to create a new token to a user who already have one, and since you've defined a OneToOneField it is not possible. I suggest you to try the get_or_create() instead of create() method.
Alternatively, you can modify the create method:
try:
auth = Authentication.objects.create(user = request.user)
except Exception as e:
#do something
return JsonResponse({'token': auth.token})
In models.py
from user.managers import MyManager
class Authentication(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE)
token = models.CharField(max_length =50, null = True)
objects = MyManager()
Then create a file 'managers.py' and put this in it:
from django.db import models
import uuid
class MyManager(models.Manager):
def create(self, user):
token = uuid.uuid4().hex
if user.authentication:
user.authentication.token = token
user.authentication.save()
return user.authentication
else:
auth = self.model(user=user, token=token)
auth.save(using=self._db)
return auth
Now if you use Authentication.objects.create(user=request.user) the create method in MyManager will be invoked. This also helps to separate token generation from views.py
Cheers!
You should have look at django builtin auth system. For your approach
try:
obj = Authentication.objects.get(user=user)
obj.token = token
obj.save()
except Authentication.DoesNotExist:
Authentication.objects.create(user = user, token = token)

Django Form use of user id

The following code is working nicely:
class SelectTwoTeams(forms.Form):
campaignnoquery = UserSelection.objects.filter(user=349).order_by('-campaignno')[:1]
currentCampaignNo = campaignnoquery[0].campaignno
cantSelectTeams = UserSelection.objects.filter(campaignno=currentCampaignNo)
currentTeams = StraightredTeam.objects.filter(currentteam = 1).exclude(teamid__in=cantSelectTeams.values_list('teamselectionid', flat=True))
team_one = forms.ModelChoiceField(queryset = currentTeams)
team_two = forms.ModelChoiceField(queryset = currentTeams)
However, you can see that the user id is currently hardcoded into the filter as 349. I would like this to be the id of the user logged in. I know in the view I can use:
currentUser = request.user
currentUserID = currentUser.id
But this code does not work within the forms section. If anyone could point me in the correct direction that would be ideal.
When I follow the suggestion below using the following form I get an error saying: NameError: name 'currentUserID' is not defined
# coding=utf-8
from dwad.threadlocals import get_current_user
from django.db.models import Max
from django import forms
from straightred.models import StraightredTeam
from straightred.models import UserSelection
class SelectTwoTeams(forms.Form):
def save(self):
currentUser = get_current_user()
currentUserID = currentUser.id
campaignnoquery = UserSelection.objects.filter(user=currentUserID).order_by('-campaignno')[:1]
currentCampaignNo = campaignnoquery[0].campaignno
cantSelectTeams = UserSelection.objects.filter(campaignno=currentCampaignNo)
currentTeams = StraightredTeam.objects.filter(currentteam = 1).exclude(teamid__in=cantSelectTeams.values_list('teamselectionid', flat=True))
team_one = forms.ModelChoiceField(queryset = currentTeams)
team_two = forms.ModelChoiceField(queryset = currentTeams)
Many thanks, Alan.
One method is to use local.threading. I have used this solution on a number of Django installations to good use.
I know there are a number of different opinions whether this is a good or bad solution. I tend to fall into the category that it can be extremely good in the right circumstances.
To set it up, create a file called threadlocals.py:
try:
from threading import local
except ImportError:
from django.utils._threading_local import local
_thread_locals = local()
def get_current_user():
return getattr(_thread_locals, 'user', None)
class ThreadLocalsMiddleware(object):
def process_request(self, request):
_thread_locals.user = getattr(request, 'user', None)
Then, add this ThreadLocalsMiddleware class to your project's middleware in settings.py:
MIDDLEWARE_CLASSES = [
...
'myproject.threadlocals.ThreadLocalsMiddleware',
...
]
Now, all you need to do is call the method get_current_user() from anywhere in your project.
from myproject.threadlocals import get_current_user
class SelectTwoTeams(forms.Form):
def save(self):
# for example:
currentUser = get_current_user()
currentUserID = currentUser.id
Found this answer at Reddit.
It is very simple and it is working good in my case.
In your view you have to include some code like this:
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user # logged in user is available on a view func's `request` instance
obj.save() # safe to save w/ user in tow