FactoryBoy "create" strategy doesn't seem to save django model - django

Probably a silly question, but I've been banging my head against a wall for a little while now.
I decided to try factory-boy library to simplify my tests and defined a factory:
from . import models
import factory
class QualtricsSurveyCacheFactory(factory.Factory):
class Meta:
model = models.QualtricsSurveyCache
survey_id = "SR_1234"
qualtrics_username = "bla#blah.bla#bla"
survey_name = "fake"
However, when I do QualtricsSurveyCacheFactory.create() it returns model with id = None
>>> survey = QualtricsSurveyCacheFactory()
>>> print survey.id
None
I can .save() model after creation, but just curious why it doesn't do it automatically.

You weren't using the correct base class for Django models. Inherit instead from:
class QualtricsSurveyCacheFactory(factory.DjangoModelFactory):
...
Then, QualtricsSurveyCacheFactory() will return a saved instance with a primary key. Use QualtricsSurveyCacheFactory.build() if you want an unsaved instance.

Related

What is the simplest way to fake a Django model field?

Currently my Django models looks like this:
class Car(Model):
horse_power = IntegerField()
I have lots of models similar to this, they are used all over the application, there are serializers of DRF endpoints using these model fields as well. Of course the field names are different in other models.
However, the horse_power value as well as other fields are no longer IntegerField, but a foreign key to another table containing more complex information like below:
class Car(Model):
horse_power_complex = OneToOneField(ComplexDataModel, on_delete=CASCADE)
To avoid lots of code change in every place the Car model is manipulated, I would like to keep the same getter and setter interfaces as before, in other words, I would like car.horse_power to return a simple integer calculated from the ComplexDataModel, and I also would like car.horse_power = 23 to access the ComplexDataModel and save the processed data so that I dont have to go over the entire application to update all references.
It is important to keep in mind that the model constructor should still work, for example, car = Car(horse_power=50).
I have tried python descriptors like below:
class HorsePower:
def __get__(self, obj, objtype=None):
return obj.horse_power_complex.get_value()
def __set__(self, obj, value):
obj.horse_power_complex.set_value(value)
class Car(Model):
horse_power = HorsePower()
horse_power_complex = OneToOneField(ComplexDataModel, on_delete=CASCADE)
This approach works well in most cases, but it fails to instantiate a new Car because the constructor does not accept non-django-field arguments.
Please do not question the reason it has to be made complex, because it really is complex, the car and horse power was just a way to make the example look clean and not confuse you with weird scientific terms
Use a property
class Car( Model)
horse_power_complex = ...
#property
def horse_power(self): # getter
return self.horse_power_complex.get_value()
#horse_power.setter
def horse_power( self, value):
self.horse_power_complex.set_value(value)

How to define django-imagekit ImageSpecFields in parent/mixin class?

I'm using django-imagekit to provide thumbnail versions of uploaded images on my Django models.
I would like to define various thumbnail ImageSpecFields in a mixin that my models can then inherit. However, each model currently has a different name for the ImageField on which its ImageSpecFields would be based.
I've tried this:
from django.db import models
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit
class ThumbnailModelMixin(models.Model):
IMAGE_SPEC_SOURCE_FIELD = None
list_thumbnail = ImageSpecField(
source=IMAGE_SPEC_SOURCE_FIELD,
processors=[ResizeToFit((80, 160)],
format="JPEG",
options={"quality": 80}
)
class Meta:
abstract = True
class Book(ThumbnailModelMixin):
IMAGE_SPEC_SOURCE_FIELD = "cover"
cover = models.ImageField(upload_to="books/")
class Event(ThumbnailModelMixin):
IMAGE_SPEC_SOURCE_FIELD = "ticket"
ticket = models.ImageField(upload_to="events/")
But this fails on page load with:
AttributeError: 'Book' object has no attribute 'list_thumbnail'
Is there a way to get inheritance like this to work?
There are at least two other solutions:
Don't use a mixin/parent, and include the ImageSpecFields on each child class - a lot of duplicate code.
Change the Book.cover and Event.ticket fields to have the same name, like image and use "image" for the ImageSpecField source parameter.
The latter sounds best, but I'm still curious as to whether there's a way to get the inheritance working?

Django: How to Properly Use ManyToManyField with Factory Boy Factories & Serializers?

The Problem
I am using a model class Event that contains an optional ManyToManyField to another model class, User (different events can have different users), with a factory class EventFactory (using the Factory Boy library) with a serializer EventSerializer. I believe I have followed the docs for factory-making and serializing, but am receiving the error:
ValueError: "< Event: Test Event >" needs to have a value for field "id"
before this many-to-many relationship can be used.
I know that both model instances must be created in a ManyToMany before linking them, but I do not see where the adding is even happening!
The Question
Can someone clarify how to properly use a ManyToManyField using models, factory boy, and serializers in a way I am not already doing?
The Set-Up
Here is my code:
models.py
#python_2_unicode_compatible
class Event(CommonInfoModel):
users = models.ManyToManyField(User, blank=True, related_name='events')
# other basic fields...
factories.py
class EventFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Event
#factory.post_generation
def users(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of users were passed in, use them
# NOTE: This does not seem to be the problem. Setting a breakpoint
# here, this part never even fires
for users in extracted:
self.users.add(users)
serializers.py
class EventSerializer(BaseModelSerializer):
serialization_title = "Event"
# UserSerializer is a very basic serializer, with no nested
# serializers
users = UserSerializer(required=False, many=True)
class Meta:
model = Event
exclude = ('id',)
test.py
class EventTest(APITestCase):
#classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user(email='test#gmail.com',
password='password')
def test_post_create_event(self):
factory = factories.EventFactory.build()
serializer = serializers.EventSerializer(factory)
# IMPORTANT: Calling 'serializer.data' is the exact place the error occurs!
# This error does not occur when I remove the ManyToManyField
res = self.post_api_call('/event/', serializer.data)
Version Info
Django 1.11
Python 2.7.10
Thank you for any help you can give!
Regarding the error:
It seems like the missing id is due to the use of .build() instead of .create() (or just EventFactory()). The former does not save the model, and therefore it does not get an id value, whereas the latter does (refer to factory docs and model docs).
I suspect the serializer still expects the object to have an id, even though the many-to-many relationship is optional, because it cannot enforce a potential relationship without an id.
However, there might be a simpler solution to the actual task. The above method is a way of generating the POST data passed to post_api_call(). If this data was instead created manually, then both the factory and serializer become unnecessary. The explicit data method might even be better from a test-perspective, because you can now see the exact data which has to produce an expected outcome. Whereas with the factory and serializer method it is much more implicit in what is actually used in the test.

'ContentType' object has no attribute 'model_class' in data migration

I have this in my data migration file:
def set_target_user(apps, schema_editor):
LogEntry = apps.get_model('auditlog', 'LogEntry')
ContentType = apps.get_model('contenttypes', 'ContentType')
for entry in LogEntry.objects.filter(target_user=None):
ct = ContentType.objects.get(id=entry.content_type.id)
model = ct.model_class()
And I got mentioned AttributeError. But it works well in other modules (not migratins). Any ideas how to overcome this?
When you call apps.get_model in a migration, you don't get the actual model class, you get a migration-specific class that is dynamically created with the fields present at that point in the migration history. It won't have any of the methods of the real ContentType model.
You should probably just use apps.get_model again to get the historical model for that content type:
model = apps.get_model(ct.app_label, ct.model)

Loose coupling of apps & model inheritance

I have a design question concerning Django. I am not quite sure how to apply the principle of loose coupling of apps to this specific problem:
I have an order-app that manages orders (in an online shop). Within this order-app I have two classes:
class Order(models.Model):
# some fields
def order_payment_complete(self):
# do something when payment complete, ie. ship products
pass
class Payment(models.Model):
order = models.ForeignKey(Order)
# some more fields
def save(self):
# determine if payment has been updated to status 'PAID'
if is_paid:
self.order.order_payment_complete()
super(Payment, self).save()
Now the actual problem: I have a more specialized app that kind of extends this order. So it adds some more fields to it, etc. Example:
class SpecializedOrder(Order):
# some more fields
def order_payment_complete(self):
# here we do some specific stuff
pass
Now of course the intended behaviour would be as follows: I create a SpecializedOrder, the payment for this order is placed and the order_payment_complete() method of the SpecializedOrder is called. However, since Payment is linked to Order, not SpecializedOrder, the order_payment_complete() method of the base Order is called.
I don't really know the best way to implement such a design. Maybe I am completely off - but I wanted to build this order-app so that I can use it for multiple purposes and wanted to keep it as generic as possible.
It would be great if someone could help me out here!
Thanks,
Nino
I think what you're looking for is the GenericForeignKey from the ContentTypes framework, which is shipped with Django in the contrib package. It handles recording the type and id of the subclass instance, and provides a seamless way to access the subclasses as a foreign key property on the model.
In your case, it would look something like this:
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class Payment(models.Model):
order_content_type = models.ForeignKey(ContentType)
order_object_id = models.PositiveIntegerField()
order = generic.GenericForeignKey('order_content_type', 'order_object_id')
You don't need to do anything special in order to use this foreign key... the generics handle setting and saving the order_content_type and order_object_id fields transparently:
s = SpecializedOrder()
p = Payment()
p.order = s
p.save()
Now, when your Payment save method runs:
if is_paid:
self.order.order_payment_complete() # self.order will be SpecializedOrder
The thing you want is called dynamic polymorphism and Django is really bad at it. (I can feel your pain)
The simplest solution I've seen so far is something like this:
1) Create a base class for all your models that need this kind of feature. Something like this: (code blatantly stolen from here)
class RelatedBase(models.Model):
childclassname = models.CharField(max_length=20, editable=False)
def save(self, *args, **kwargs):
if not self.childclassname:
self.childclassname = self.__class__.__name__.lower()
super(RelatedBase, self).save(*args, **kwargs)
#property
def rel_obj(self):
return getattr(self, self.childclassname)
class Meta:
abstract = True
2) Inherit your order from this class.
3) Whenever you need an Order object, use its rel_obj attribute, which will return you the underlying object.
This solution is far from being elegant, but I've yet to find a better one...