django factory boy factory with OneToOne relationship and related field - python

I am using Factory Boy to create test factories for my django app. The model I am having an issue with is a very basic Account model which has a OneToOne relation to the django User auth model (using django < 1.5):
# models.py
from django.contrib.auth.models import User
from django.db import models
class Account(models.Model):
user = models.OneToOneField(User)
currency = models.CharField(max_length=3, default='USD')
balance = models.CharField(max_length="5", default='0.00')
Here are my factories:
# factories.py
from django.db.models.signals import post_save
from django.contrib.auth.models import User
import factory
from models import Account
class AccountFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = Account
user = factory.SubFactory('app.factories.UserFactory')
currency = 'USD'
balance = '50.00'
class UserFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = User
username = 'bob'
account = factory.RelatedFactory(AccountFactory)
So I am expecting the factory boy to create a related UserFactory whenever AccountFactory is invoked:
# tests.py
from django.test import TestCase
from factories import AccountFactory
class AccountTest(TestCase):
def setUp(self):
self.factory = AccountFactory()
def test_factory_boy(self):
print self.factory.id
When running the test however, it looks like multiple User models are being create, and I am seeing an integriy error:
IntegrityError: column username is not unique
The documentation does mention watching out for loops when dealing with circular imports, but I am not sure whether that is whats going on, nor how I would remedy it. docs
If anyone familiar with Factory Boy could chime in or provide some insight as to what may be causing this integrity error it would be much appreciated!

I believe this is because you have a circular reference in your factory definitions. Try removing the line account = factory.RelatedFactory(AccountFactory) from the UserFactory definition. If you are always going to invoke the account creation through AccountFactory, then you shouldn't need this line.
Also, you may consider attaching a sequence to the name field, so that if you ever do need more than one account, it'll generate them automatically.
Change: username = "bob" to username = factory.Sequence(lambda n : "bob {}".format(n)) and your users will be named "bob 1", "bob 2", etc.

To pass result of calling UserFactory to AccountFactory you should use factory_related_name (docs)
Code above works next way:
AccountFactory for instantiating needs SubFactory(UserFactory).
UserFactory instantiates User.
UserFactory after instantiating calls RelatedFactory(AccountFactory)
Recursion,.. that is broken due to unique username constraint (you probably want to generate usernames via FuzzyText or Sequence)
So you need write UserFactory like this:
class UserFactory(factory.django.DjangoModelFactory):
account = factory.RelatedFactory(AccountFactory, factory_related_name='user')
username = factory.Sequence(lambda a: 'email%04d#somedomain.com' % a)
# rest of code
But you can still experience issues with already written tests. Imagine you have in tests places like next:
user = UserFactory()
account = Account(user=user)
Then adding RelatedFactory will break tests. If you haven't lots of tests and contributors in your project, you could rewrite them. But if not, it is not an option. Here is how it could be handled:
class UserFactory(factory.django.DjangoModelFactory):
class Params:
generate_account = factory.Trait(
account=factory.RelatedFactory(AccountFactory, factory_related_name='user')
)
Then code above won't be broken, because default call of UserFactory won't instantiate AccountFactory. To instantiate user with account:
user_with_account = UserFactory(generate_account=True)

Related

how to override the update method of a model in django

I am using django with python. I am trying to update the model whenever a field is updated, in this case because i have a lambda function in the cloud, i want when a postgres query update an instance of the model, during the update action, update the age of the Person model below:
data
Contact table
id = 1
name = 'john'
age = 38
sql
UPDATE contacts_contact SET name = 'jane' where id = '1'; # this works fine
now i want to make sure that when the name is changed to jane as above, that the age update automatically in django with the override method
django
class Contact(models.Model):
..
name = models.CharField()
age = models.IntegerField()
def update(self, *args, **kwargs):
if self.age:
self.age = 25
super().update(*args, **kwargs) # i tried this
super(Contact, self).update(*args, **kwargs) # i tried this too
both update methods i tried above do not update the age of the person regardless of the fact that the sql query update worked
is there something that i am missing?
PS: I want to update that field specifically in django, not in the sql query
"I am using django with python. I am trying to update the model whenever a field is updated" if this is the case django signals can help you, in-fact they are built for this purpose only.
follow the below to steps to enable the django-signals for your models.
Inside your_app/app.py you can find the snippets as below. if not paste the same and modify it with your app name. here you're just importing the signals(contents in your_app/signal.py)
from django.apps import AppConfig
class YourAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'your_app'
verbose_name = 'Your App'
def ready(self):
import your_app.signals
Inside your_app/signals.py paste the contents below in it. modify it with your own model name. for now I'll use your Contact model.
from django.db.models.signals import post_save
from your_app.models import Contact
from django.dispatch import receiver
#receiver(post_save, sender=Contact)
def create_user(sender, instance, created, **kwargs):
print(sender)
print(instance)
print(kwargs)
if created:
print('Hurray its created', created)
here you can use post_save signal. Django includes a “signal dispatcher” which helps decoupled applications get notified when actions occur elsewhere in the framework. In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place.
whenever you call .save() method of Contact model(create/update). for example -
c = Contact(name='zzz', age=20)
c.save()
now this will make the post_save signal to get notified about the changes, you can verify the same in your create_user() method print statements. from here you can do whatever you want with your model.
you can refer more about post_save signals here https://docs.djangoproject.com/en/4.0/ref/signals/#post-save

Why isn't django.contrib.auth.authenticate() working here?

I'm writing a simple (for now) Django app. I'm having trouble with authentication: I'm trying to use all of the out-of-the-box components, and both in the app itself and in tests, I can't authenticate users. So, for example, here's a test that fails:
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.test import TestCase
[...]
class UserTestCase(TestCase):
def setUp(self):
self.testu = User(username="thename", password="thepassword", first_name="thefirstname")
self.testu.save()
def testAuthenticate(self):
u = authenticate(username="thename", password="thepassword")
self.assertEqual(u.first_name, "thefirstname")
I get an AttributeError:
'NoneType' object has no attribute "first_name".
I think this is because authenticate() is returning None (representing that there is no such user).
This fails whether or not I include the line "self.testu.save()".
I have other tests that pass, so I don't think the problem is with the test infrastructure. I can successfully create users and retrieve their information from the database.
The only mention of User in models.py is:
from django.contrib.auth.models import User
I've read through a lot of documentation but can't figure out what's going on. Can anyone help? Thanks in advance.
You can not create a User object with a password like that. The password needs to be hashed. Therefore, you should use the .set_password(..) method [Django-doc]:
class UserTestCase(TestCase):
def setUp(self):
self.testu = User(username="thename", first_name="thefirstname")
self.testu.set_password("thepassword")
self.testu.save()
# …

notify user if similar model created _Django

I have a model.that I need to if any model with specific field created in database.send a Email to user for notify.I did some search too many apps are there for handling the notify. thats not my concern .I dont know how deploy this structure.any guide or example for this.for example :
if x = book.objects.create(title="book1") :
print("the book created")
if this action happend do something.
If you need to monitor object creation globally the best thing to use is signals
Like so:
from .models import Book
from django.db.models.signals import post_save
def book_created(sender, instance, created, **kwargs):
if created and instance.title == 'book1':
#logic here
post_save.connect(save_profile, sender=Book)
you need to stick that post_save.connect() function somewhere where it will be evaluated when the app is run, you can use app_dir/app.py for instance.

Django form not calling clean_<fieldname> (in this case clean_email)

I couldn't find an answer to the following question, it took me a couple of hours to find out, hence I'm adding it. I'll add my approach of solving it and the answer.
I'm following a YouTube tutorial from This person. For some reason I'm typing the same code, and I checked every single letter. Yet for some reason my cleaning functions aren't called. It's probably something simple, especially since a related question showed something similar. It's probably a framework thing that I get wrong, but I wouldn't know what it is.
Here is the relevant code.
forms.py (complete copy/paste from his Github)
from django import forms
from .models import SignUp
class ContactForm(forms.Form):
full_name = forms.CharField(required=False)
email = forms.EmailField()
message = forms.CharField()
class SignUpForm(forms.ModelForm):
class Meta:
model = SignUp
fields = ['full_name', 'email']
### exclude = ['full_name']
def clean_email(self):
email = self.cleaned_data.get('email')
email_base, provider = email.split("#")
domain, extension = provider.split('.')
# if not domain == 'USC':
# raise forms.ValidationError("Please make sure you use your USC email.")
if not extension == "edu":
raise forms.ValidationError("Please use a valid .EDU email address")
return email
# Final part is ommited, since it's not relevant.
admin.py (typed over from the tutorial)
from django.contrib import admin
# Register your models here.
from .models import SignUp
from .forms import SignUpForm
class SignUpAdmin(admin.ModelAdmin):
list_display = ['__unicode__', 'timestamp', 'updated']
class Meta:
model = SignUp
form = SignUpForm
admin.site.register(SignUp, SignUpAdmin)
After using print statements for a while and reading questions that seemed similar but eventually didn't solve my problem, I decided to look into the source of Django (idea inspired by the most similar question I could find).
Then, I decided to debug the source, since I wanted to know how Django is treating my customized function (thanks to a tutorial + SO answer). In the source I found that the customized functions were called around return super(EmailField, self).clean(value) (line 585, django/forms/fields.py, Django 1.8). When I was stepping through the code I found the critical line if hasattr(self, 'clean_%s' % name): (line 409, django/forms/forms.py, Django 1.8). I checked for the value name which was "email". Yet, the if-statement evaluated as False ((Pdb) p hasattr(self, 'clean_%s' % name)). I didn't understand why, until I figured out that the function name was not registered ((Pdb) pp dir(self)).
I decided to take a look at the whole source code repository and cross-checked every file and then I found that
class Meta:
model = SignUp
form = SignUpForm
means that form / SignUpForm were nested inside the Meta class. At first, I didn't think much of it but slowly I started to realize that it should be outside the Meta class while staying main class (SignUpAdmin).
So form = SignUpForm should have been idented one tab back. For me, as a Django beginner, it still kind of baffles me, because I thought the Meta class was supposed to encapsulate both types of data (models and forms). Apparently it shouldn't, that's what I got wrong.

Django custom registration fields

I'm becoming increasingly bewildered by the range of answers on offer to the seemingly simple problem of adding custom fields to the django-registration register form/flow. This should be a default, documented aspect of the package (not to sound ungrateful, just that it is such a well-equipped package), but solutions to the problem are dizzying.
Can anyone give me the most simple solution to getting UserProfile model data included in the default registration register page?
Update:
I eventually used Django Registration's own signals to give me this hacky fix. It is particularly ugly because, I had to use try on the POST attribute dealing with my Boolean since I found that the checkbox returned nothing if left empty.
Would appreciate any advice on improving this, or best practice.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)
Thanks
What you have looks like a workable approach.
I've looked through the django-registration code, and based on the following comments in the register view I've come up with another solution. I'm not totally sure this is cleaner, but if you aren't a fan of signals this is good. This also provides a much easier avenue if you intend to make more customizations.
# from registration.views.register:
"""
...
2. The form to use for account registration will be obtained by
calling the backend's ``get_form_class()`` method, passing the
``HttpRequest``. To override this, see the list of optional
arguments for this view (below).
3. If valid, the form's ``cleaned_data`` will be passed (as
keyword arguments, and along with the ``HttpRequest``) to the
backend's ``register()`` method, which should return the new
``User`` object.
...
"""
You could create a custom backend and override those mentioned methods:
# extend the provided form to get those fields and the validation for free
class CustomRegistrationForm(registration.forms.RegistrationForm):
receive_email = forms.BooleanField(initial=True, required=False)
# again, extend the default backend to get most of the functionality for free
class RegistrationBackend(registration.backends.default.DefaultBackend):
# provide your custom form to the registration view
def get_form_class(self, request):
return CustomRegistrationForm
# replace what you're doing in the signal handler here
def register(self, request, **kwargs):
new_user = super(RegistrationBackend, self).register(request, **kwargs)
# do your profile stuff here
# the form's cleaned_data is available as kwargs to this method
profile = new_user.userprofile
# use .get as a more concise alternative to try/except around [] access
profile.receive_email = kwargs.get('receive_email', False)
profile.save()
return new_user
To use the custom backend, you can then provide separate urls. Before including the default urls, write 2 confs that point at your custom backend. Urls are tested in the order defined, so if you define these two before including the defaults, these two will capture before the default ones are tested.
url(r'^accounts/activate/(?P<activation_key>\w+)/$',
activate,
{'backend': 'my.app.RegistrationBackend'},
name='registration_activate'),
url(r'^accounts/register/$',
register,
{'backend': 'my.app.RegistrationBackend'},
name='registration_register'),
url(r'^accounts/', include('registration.backends.default.urls')),
The docs actually describe all this, but they aren't particularly accessible (no readthedocs). They are all included in the project, and I was browsing them here.
I eventually used Django Registration's own signals to give me this fix.
I will clean up the try/except flow at some point. dokkaebi also points out above that I might be able to assess the request.GET parameters for when a checkbox is left empty.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)

Categories

Resources