Factory Boy vs. custom objects - python

The question:
What are the advantages of using Factory Boy in the following situation? I don't really see why I shouldn't just deliver my own custom objects. If I am wrong please show me why.
I am using Factory Boy to make user instances during my tests, which creates a UserProfile object dynamically (standard recipe from Factory_Boy documentation).
The Data class creates data that will be delivered to forms during a post (other methods I'm using deliver data for self.client.post methods that login, register, and activate users. Unless I'm missing something, I'd have to build a separate DjangoModelFactory subclass for each situation in order to use ClassName.attributes() where the data requirements differ. The other reason I went in this direction is that UserProfile has a User foreign key, so I wasn't able to call UserProfileFactory.attributes() directly, only UserFactory.attributes(). Why not just make my own like I'm doing?
#Factories.py
IMAGE_PATH = os.path.join(os.path.dirname(__file__),
'../../test_files/test_images/image.jpeg')
class UserProfileFactory(DjangoModelFactory):
FACTORY_FOR = UserProfile
user = factory.SubFactory('portal.factories.UserFactory', profile=None)
first_name = factory.Sequence(lambda n: "Joe_%d" % n)
last_name = factory.Sequence(lambda n: "Schmoe_%d" % n)
nickname = factory.Sequence(lambda n: "JoeBlow_%d" % n)
profile_image = factory.LazyAttribute(lambda t: File(open(IMAGE_PATH)))
class UserFactory(DjangoModelFactory):
FACTORY_FOR = User
username = factory.Sequence(lambda n: "user_%d" % n)
password = make_password("password")
email = factory.Sequence(lambda n: "user_%d#gmail.com" % n)
profile = factory.RelatedFactory(UserProfileFactory, 'user')
#classmethod
def _generate(cls, create, attrs):
models.signals.post_save.disconnect(user_post_save, sender=User)
user = super(UserFactory, cls)._generate(create, attrs)
models.signals.post_save.connect(user_post_save, sender=User)
return user
class Data(object):
def __init__(self):
self.IMAGE_PATH = os.path.join(os.path.dirname(__file__),
'../../test_files/test_images/image.jpeg')
self.profile_image = File(open(IMAGE_PATH))
def get_profile_update(self, user):
return {'first_name': 'Jeff',
'last_name': 'Lebowski',
'nickname': 'The Dude',
'profile_image': self.profile_image,
'user': user.pk,}
def and_so_on(self):
continues...
Then I am using data like this in the following context during my integration tests:
class PortalTestCase(TestCase):
"""Shortened and simplified"""
def test_edit_profile_post(self):
user = UserFactory.create()
login_bool = self.client.login(username=user.username,
password=self.data.get_password())
data = self.data.get_profile_update(user)
response = self.client.post(reverse(self.get_edit_profile()),
data=data,
follow=True)
success_url = 'http://testserver%s' % reverse(self.get_portal())
template_name = self.get_portal_template()
content_text_img = 'src="/' + user.get_profile().profile_image.url + '"'
self.assertRedirects(response, success_url)
self.assertTemplateUsed(response, template_name)
self.assertContains(response, content_text_img)

You are correct that in this single instance test case factory boy may be overkill. However think about the application as whole. More often than not you will need to use another UserFactory object. Guess what that footwork has already been done. The reason I choose to use factory boy is consistent reusability.
As the documentation lays out but doesn't exactly spell out this tool is meant to grow with your application and not replace a single isolated tescase.

Related

How to make Django run a command only once per model

We're using django-MPTT for tree items and I have to create functionality for automatic email notifications when a tree item has been modified. Absolutely straight-forward using save signals but - if a parent item is modified, then n (n being number of childen items + parent item) emails are sent instead of just one (because parent's changes are automatically done to children). Is there a good way to prevent that and basically tell Django "Please send only one email per model"?
The best I've come up with is a kind of a hacky way (and a pretty bad hack): add a model to the database that is updated when the parent item is modified and with childrens always check that model before sending email. I.e. if HiddenModel exists, do not send email, else send email. And after like five seconds (probably in another thread) remove/undo modifications to that HiddenModel object. It would probably work, but performance-wise it's just bad, with all those database queries.
EDIT: Model: (shortened version for SO, probably has some errors in this form)
from mptt.models import MPTTModel, TreeForeignKey
class TreeItem(MPTTModel):
parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
users = models.ManyToManyField('accounts.User', blank=True)
name = models.CharField(max_length=255)
#property
def tree_path(self):
if self.is_root_node():
return []
tree = self.get_ancestors(include_self=False, ascending=False)
return list(map(lambda item: item.name, tree))
def save(self, *args, **kwargs):
is_new = self.pk is None
if not is_new:
prev_allowed_users = TreeItem.objects.get(pk=self.pk).users.all()
super().save(*args, **kwargs)
new_allowed_users = self.users.all()
if is_new:
self.send_access_email(list(new_allowed_users)) #TODO
else:
if prev_allowed_users != new_allowed_users:
send_to = self.get_new_access_users(prev_allowed_users, new_allowed_users) # TODO
self.send_access_email(send_to)
def get_new_access_users(self, prev_users, new_users):
send_to = []
for user in new_users:
if user not in prev_users:
send_to.append(user)
return send_to
def send_access_email(self, recipient_list):
email_content = 'example'
email = EmailMessage(
'subject',
email_content,
'ex#ample.com',
recipient_list)
print("Sending email to %s users" % str(len(recipient_list)))
You need some sort of registry to figure out if your object is being saved externally or as part of a tree traversing cascade. It seems that using a class variable might be a more elegant way of doing this. Here is some pseudo-code to illustrate what I mean.
class TreeItem(MPTTModel):
_save_registry = set()
... your model fields go here ...
def is_save_locked(self):
return any(p.id in TreeItem._save_registry for p in self.get_parents())
def save_lock(self):
TreeItem._save_registry.add(self.id)
def save_release(self):
TreeItem._save_registry.remove(self.id)
def save(self, *args, **kwargs):
if not self.is_save_locked:
self.save_lock()
... do things only original save should do ...
self.save_release()
... do things every save should do ...
return super(TreeItem, self).save(*args, **kwargs)

Finding objects without relationship in django

I am learning Django, and want to retrieve all objects that DON'T have a relationship to the current object I am looking at.
The idea is a simple Twitter copycat.
I am trying to figure out how to implement get_non_followers.
from django.db import models
RELATIONSHIP_FOLLOWING = 1
RELATIONSHIP_BLOCKED = 2
RELATIONSHIP_STATUSES = (
(RELATIONSHIP_FOLLOWING, 'Following'),
(RELATIONSHIP_BLOCKED, 'Blocked'),
)
class UserProfile(models.Model):
name = models.CharField(max_length=200)
website = models.CharField(max_length=200)
email = models.EmailField()
relationships = models.ManyToManyField('self', through='Relationship',
symmetrical=False,
related_name='related_to')
def __unicode__ (self):
return self.name
def add_relationship(self, person, status):
relationship, created = Relationship.objects.get_or_create(
from_person=self,
to_person=person,
status=status)
return relationship
def remove_relationship(self, person, status):
Relationship.objects.filter(
from_person=self,
to_person=person,
status=status).delete()
return
def get_relationships(self, status):
return self.relationships.filter(
to_people__status=status,
to_people__from_person=self)
def get_related_to(self, status):
return self.related_to.filter(
from_people__status=status,
from_people__to_person=self)
def get_following(self):
return self.get_relationships(RELATIONSHIP_FOLLOWING)
def get_followers(self):
return self.get_related_to(RELATIONSHIP_FOLLOWING)
def get_non_followers(self):
# How to do this?
return
class Relationship(models.Model):
from_person = models.ForeignKey(UserProfile, related_name='from_people')
to_person = models.ForeignKey(UserProfile, related_name='to_people')
status = models.IntegerField(choices=RELATIONSHIP_STATUSES)
This isn't particularly glamorous, but it gives correct results (just tested):
def get_non_followers(self):
UserProfile.objects.exclude(to_people=self,
to_people__status=RELATIONSHIP_FOLLOWING).exclude(id=self.id)
In short, use exclude() to filter out all UserProfiles following the current user, which will leave the user themselves (who probably shouldn't be included) and all users not following them.
i'v been searching for a method or some way to do that for like an hour, but i found nothing.
but there is a way to do that.
you can simply use a for loop to iterate through all objects and just remove all objects that they have a special attribute value.
there is a sample code here:
all_objects = className.objects.all()
for obj in all_objects:
if obj.some_attribute == "some_value":
all_objects.remove(obj)
Solution to the implementation of get_non_followers:
def get_non_following(self):
return UserProfile.objects.exclude(to_person__from_person=self, to_person__status=RELATIONSHIP_FOLLOWING).exclude(id=self.id)
This answer was posted as an edit to the question Finding objects without relationship in django by the OP Avi Meir under CC BY-SA 3.0.
current_userprofile = current_user.get_profile()
rest_of_users = Set(UserProfile.objects.filter(user != current_userprofile))
follow_relationships = current_userprofile.relationships.filter(from_person=current_user)
followers = Set();
for follow in follow_relationships:
followers.add(follow.to_person)
non_followeres = rest_of_users.difference(followers)
Here non_followers is the list of userprofiles you desire. current_user is the user whose non_followers you are trying to find.
I haven't tested this out, but it think it should do what you want.
def get_non_followers(self):
return self.related_to.exclude(
from_people__to_person=self)

Django Tastypie throws a 'maximum recursion depth exceeded' when full=True on reverse relation.

I get a maximum recursion depth exceeded if a run the code below:
from tastypie import fields, utils
from tastypie.resources import ModelResource
from core.models import Project, Client
class ClientResource(ModelResource):
projects = fields.ToManyField(
'api.resources.ProjectResource', 'project_set', full=True
)
class Meta:
queryset = Client.objects.all()
resource_name = 'client'
class ProjectResource(ModelResource):
client = fields.ForeignKey(ClientResource, 'client', full=True)
class Meta:
queryset = Project.objects.all()
resource_name = 'project'
# curl http://localhost:8000/api/client/?format=json
# or
# curl http://localhost:8000/api/project/?format=json
If a set full=False on one of the relations it works. I do understand why this is happening but I need both relations to bring data, not just the "resource_uri". Is there a Tastypie way to do it? I managed to solve the problem creating a serialization method on my Project Model, but it is far from elegant. Thanks.
You would have to override full_dehydrate method on at least one resource to skip dehydrating related resource that is causing the recursion.
Alternatively you can define two types of resources that use the same model one with full=Trueand another with full=False.
Thanks #astevanovic pointing the right direction.
I found that overriding dehydrate method to process only some specified fields is a bit less tedious than overriding full_hydrate method to skip fields.
In the pursuit of reusability, I came up with the following code snippets. Hope it would be useful to some:
class BeeModelResource(ModelResource):
def dehydrate(self, bundle):
bundle = super(BeeModelResource, self).dehydrate(bundle)
bundle = self.dehydrate_partial(bundle)
return bundle
def dehydrate_partial(self, bundle):
for field_name, resource_field in self.fields.items():
if not isinstance(resource_field, RelatedField):
continue
if resource_field.full: # already dehydrated
continue
if not field_name in self._meta.partial_fields:
continue
if isinstance(resource_field, ToOneField):
fk_object = getattr(bundle.obj, resource_field.attribute)
fk_bundle = Bundle(obj=fk_object, request=bundle.request)
fk_resource = resource_field.get_related_resource(fk_object)
bundle.data[field_name] = fk_resource.dehydrate_selected(
fk_bundle, self._meta.partial_fields[field_name]).data
elif isinstance(resource_field, ToManyField):
data = []
fk_objects = getattr(bundle.obj, resource_field.attribute)
for fk_object in fk_objects.all():
fk_bundle = Bundle(obj=fk_object, request=bundle.request)
fk_resource = resource_field.get_related_resource(fk_object)
fk_bundle = fk_resource.dehydrate_selected_fields(
fk_bundle, self._meta.partial_fields[field_name])
data.append(fk_bundle.data)
bundle.data[field_name] = data
return bundle
def dehydrate_selected_fields(self, bundle, selected_field_names):
# Dehydrate each field.
for field_name, field_object in self.fields.items():
# A touch leaky but it makes URI resolution work.
# (borrowed from tastypie.resources.full_dehydrate)
if field_name in selected_field_names and not self.is_special_fields(field_name):
if getattr(field_object, 'dehydrated_type', None) == 'related':
field_object.api_name = self._meta.api_name
field_object.resource_name = self._meta.resource_name
bundle.data[field_name] = field_object.dehydrate(bundle)
bundle.data['resource_uri'] = self.get_resource_uri(bundle.obj)
bundle.data['id'] = bundle.obj.pk
return bundle
#staticmethod
def is_special_fields(field_name):
return field_name in ['resource_uri']
With #sigmus' example, the resources will need 3 modifications:
both resource will use BeeModuleResource as its super class (or, add dehydrate_partial to one resource and dehydrate_selected to the other.)
unset full=True on either of the resource
add partial_fields into the resource Meta the unset resource
```
class ClientResource(BeeModelResource): # make BeeModelResource a super class
projects = fields.ToManyField(
'api.resources.ProjectResource', 'project_set'
) # remove full=True
class Meta:
queryset = Client.objects.all()
resource_name = 'client'
partial_fields = {'projects': ['memo', 'title']} # add partial_fields
class ProjectResource(BeeModelResource): # make BeeModelResource a super class
client = fields.ForeignKey(ClientResource, 'client', full=True)
class Meta:
queryset = Project.objects.all()
resource_name = 'project'
Dead simple solution: set the use_in = 'list' kwarg on both relationship fields!
The docs: http://django-tastypie.readthedocs.org/en/latest/fields.html#use-in

Massage model data before save in Django

I'm not sure if this is the best way to do this, but I have some data that is being sent by a form. I have a ModelForm that takes the the request.POST of that form data. All the data that is being sent is a description, amount and deposit (boolean).
When the person submits the data, the amount will be a positive number, but I would like to store it in the database as a negative number if deposit is False.
I was thinking of doing this in either the Model or the ModelForm and sort of massage that amount before saving... So, somewhere in one of those classes, I'd like to have something like:
if not deposit:
amount = -amount
... and then save it as such.
Is there a way to handle this in the ModelForm or Model that would keep me from having to do all that logic inside of the view?
ModelForm's save() method is a good place for this:
class MyForm(models.ModelForm):
...
def save(self):
instance = super(MyForm, self).save(commit=False)
if not self.deposit:
self.amount = -self.amount
instance.save()
return instance
Overwrite model save method is a solution. But I prefear make this operations in clean method and mix it with business rules:
models.py:
from django.db import models
class Issue(models.Model):
....
def clean(self):
rules.Issue_clean(self)
from issues import rules
rules.connect()
rules.py:
from issues.models import Issue
def connect():
from django.db.models.signals import post_save, pre_save, pre_delete
#issues
pre_delete.connect(Issue_pre_delete, sender= Incidencia)
pre_save.connect(Issue_pre_save, sender = Incidencia )
post_save.connect(Issue_post_save, sender = Incidencia )
def Incidencia_clean( instance ):
#pre save:
if not instance.deposit:
instance.amount *= -1
#business rules:
errors = {}
#dia i hora sempre informats
if not instance.account.enoughCredit:
errors.append( 'No enough money.' )
if len( errors ) > 0:
raise ValidationError(errors)
def Issue_pre_save(sender, instance, **kwargs):
instance.clean()
At this way rules are binded to model and you don't need to write code on each form that this model appears (here, you can see this on more detail)

email whitelist/blacklist in python/django

I am writing a django app that keeps track of which email addresses are allowed to post content to a user's account. The user can whitelist and blacklist addresses as they like.
Any addresses that aren't specified can either be handled per message or just default to whitelist or blacklist (again user specified).
Here are the django models I wrote... do you think is a good way to do it? or should I add a whitelist and blacklist field to each user's profile model?
class knownEmail(models.Model):
# The user who set this address' permission, NOT
# the user who the address belongs to...
relatedUser = models.ManyToManyField(User)
email = models.EmailField()
class whiteList(knownEmail):
pass
class blackList(knownEmail):
pass
Then I could do something like:
def checkPermission(user, emailAddress):
"Check if 'emailAddress' is allowed to post content to 'user's profile"
if whiteList.objects.filter(relatedUser=user, email=emailAddress):
return True
elif blackList.objects.filter(relatedUser=user, email=emailAddress):
return False
else:
return None
Is there a better way?
I would restructure it so both lists were contained in one model.
class PermissionList(models.Model):
setter = models.ManyToManyField(User)
email = models.EmailField(unique=True) #don't want conflicting results
permission = models.BooleanField()
Then, your lists would just be:
# whitelist
PermissionList.objects.filter(permission=True)
# blacklist
PermissionList.objects.filter(permission=False)
To check a particular user, you just add a couple functions to the model:
class PermissionList(...):
...
#classmethod
def is_on_whitelist(email):
return PermissionList.objects.filter(email=email, permission=True).count() > 0
#classmethod
def is_on_blacklist(email):
return PermissionList.objects.filter(email=email, permission=False).count() > 0
#classmethod
def has_permission(email):
if PermissionList.is_on_whitelist(email):
return True
if PermissionList.is_on_blacklist(email):
return False
return None
Having everything in one place is a lot simpler, and you can make more interesting queries with less work.
[Please start All Class Names With Upper Case Letters.]
Your code doesn't make use of your class distinction very well.
Specifically, your classes don't have any different behavior. Since both classes have all the same methods, it isn't clear why these are two different classes in the first place. If they have different methods, then your solution is good.
If, however, they don't have different methods, you might want to look at providing a customized manager for each of the two subsets of KnownEmail
class WhiteList( models.Manager ):
def get_query_set( self ):
return super( WhiteList, self ).get_query_set().filter( status='W' )
class BlackList( models.Manager )
def get_query_set( self ):
return super( BlackList, self ).get_query_set().filter( status='B' )
class KnownEmail( models.Model ):
relatedUser = models.ForeignKey(User)
email = models.EmailField()
status = models.CharField( max_length=1, choices=LIST_CHOICES )
objects = models.Manager() # default manager shows all lists
whiteList= WhiteList() # KnownEmail.whiteList.all() is whitelist subset
blackList= BlackList() # KnownEmail.blackList.all() is blackList subset
This class compares an email address with a blacklist of email domains. If you preffer you can download this module using pip install django-email-blacklist.
from django.conf import settings
import re
class DisposableEmailChecker():
"""
Check if an email is from a disposable
email service
"""
def __init__(self):
self.emails = [line.strip() for line in open(settings.DISPOSABLE_EMAIL_DOMAINS)]
def chunk(self, l, n):
return (l[i:i + n] for i in range(0, len(l), n))
def is_disposable(self, email):
for email_group in self.chunk(self.emails, 20):
regex = "(.*" + ")|(.*".join(email_group) + ")"
if re.match(regex, email):
return True
return False

Categories

Resources