Trying to test a nested serilailzer, how to create required subfactories? - python

I am getting a failing test because (I think) I can't add a subfactory field.
I have a serializer that calls on another serializer. I am finding it difficult to replicate this in a test (specifically the factory). The issue is caused by my test: _expected_restuarant_response. It is expecting a response that includes an employee field (like the serializer does). The problem is that I can't add this subfactory field to RestaurantFactory because RestaurantFactory is located above EmployeeFactory in the factories.py file (EmployeeFactory has not yet been initialised at that point in the file). I also can't put EmployeeFactory higher than RestaurantFactory because it refers to RestaurantFactory.
Could somebody point me in the right direction? Thank you.
A simplified version of my files:
models.py:
class Restaurant(models.Model):
name = models.CharField(max_length=20)
class Employee(models.Model):
badge_id = models.CharField(max_length=20)
restaurant = models.ForeignKey(Restaurant)
serializers.py:
class EmployeeSerializer(serializers.ModelSerializer):
class Meta:
model = Employee
fields = [
'badge_id']
class RestaurantSerializer(serializers.ModelSerializer):
employee = EmployeeSerializer(many=True, read_only=True)
class Meta:
model = Restaurant
fields = [
'name',
'employee']
factories.py:
class RestaurantFactory(factory.django.DjangoModelFactory):
name = factory.Faker('company')
< ----------------------------------- do I need to add an Employee subfactory here? If so how?
class EmployeeFactory(factory.django.DjangoModelFactory):
badge_id = factory.Faker('id')
restaurant = factory.SubFactory(RestaurantFactory)
test.py:
def _expected_restuarant_response(restaurant):
return {
restaurant.name,
restaurant.employee_set.badge_id <---- the test fails here because the RestaurantFactory does not include this field
}
assert response_json == [_expected_restaurant_response(RestaurantFactory())]

In terms of the Factory creation you could probably do something like this:
class RestaurantFactory(factory.django.DjangoModelFactory):
name = factory.Faker('company')
employee = factory.SubFactory('path.to.EmployeeFactory', employee=None)
then on EmployeeFactory do this:
class EmployeeFactory(factory.django.DjangoModelFactory):
badge_id = factory.Faker('id')
restaurant = factory.RelatedFactory(RestaurantFactory, factory_related_name='employee')

Related

How can I mock django model object?

For example, I have a lot of interrelated tables in my project
class A(models.Model):
name = models.models.CharField(max_length=16)
class B(models.Model):
name = models.models.CharField(max_length=16)
a = models.ForeignKey(A, on_delete=models.CASCADE)
class C(models.Model):
name = models.models.CharField(max_length=16)
b = models.ForeignKey(B, on_delete=models.CASCADE)
and so on.
I need to test model C and have no interest in A and B models. Is there any chance to mock model B that can be used when creating model C objects?
I mean I'd like to create few objects without building a massive base just to test one tiny model.
You can use bakery
from model_bakery import baker
AmodelInstance = baker.make(A)
If it is only for some tests and you only need a few instances you could do something like this:
model_a_instance = A(name='somename')
model_b_instance = B(name='somename2', a=model_a_instance)
model_c_instance = C(name='somename3', b=model_b_instance)
Nonetheless, if you need to save model_c in the DB make sure to save model_a and model_b also.
If you are going to need these models more extensively I would recommend creating a factory class to populate your models.
Hopefully this helped.
After couple of days of research I don't have an answer to my question, but I think I found a tool that takes care of 'preparing the base' before actual testing or greatly simplifies it.
I have about 1000 lines of test code in my project and I decided to switch to pytest and rewrite tests almost from scratch, so, I'm actually doing what I hoped to avoid, but it's acceptable for me in this case.
Instead of creating queryset objects via django ORM I utilize Factory Boy to define factories that resolve database relations in the way I want.
For the example case I would implement something like that:
import factory
class AFactory(factory.django.DjangoModelFactory):
class Meta:
model = A
class BFactory(factory.django.DjangoModelFactory):
class Meta:
model = B
a = factory.SubFactory(AFactory)
class CFactory(factory.django.DjangoModelFactory):
class Meta:
model = C
b = factory.SubFactory(BFactory)
So, to test C model you're just creating C model object via CFactory.create(), and all 'chain' stuff is handled using SubFactories.
However, example models have no any constraints, unlike real database tables. Here's an example for more descriptive answer:
class Country(models.Model):
name = models.CharField(choices=(('BY', 'Belarus'), ('UA', 'Ukraine')),
max_length=64, unique=True)
class Employee(models.Model):
name = models.CharField(max_length=64)
email = models.EmailField(max_length=100, unique=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
class Supervisor(models.Model):
name = models.CharField(max_length=64)
email = models.EmailField(max_length=100, unique=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
If I would use same approach as with A B C example and executed something like SupervisorFactory.create() at least 2 times I violated Country.name and Employee.email constraints. 2 SupervisorFactory calls would try to create 4 countries, but it's possibly to create only 3 - 'BY', 'UA', and empty string that is factory boy's default. Thus, you should specify the rules factories generate data by:
class CountryFactory(factory.django.DjangoModelFactory):
class Meta:
model = Country
django_get_or_create = ('name',)
name = factory.Iterator('BY', 'UA')
class EmployeeFactory(factory.django.DjangoModelFactory):
class Meta:
model = Employee
email = factory.Sequence(lambda nums: 'employee.%04d#employee.io' % nums)
country = factory.SubFactory(CountryFactory)
class Supervisor(factory.django.DjangoModelFactory):
class Meta:
model = Supervisor
email = factory.Sequence(lambda nums: 'supervisor.%04d#supervisor.io' % nums)
country = factory.SubFactory(CountryFactory)
employee = factory.SubFactory(EmployeeFactory)
Hope this will save your time.

How to create 2 objects from separate models with a single serializer and also retrieve them from the database with a single serializer in Django RF?

I have 3 models: Maker, Item and MakerItem that creates the relation between the items and their makers:
class Maker(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=100)
class Item(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=100)
class MakerItem(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
item_id = models.ForeignKey(Item, on_delete=models.CASCADE)
maker_id = models.ForeignKey(Maker, on_delete=models.CASCADE)
the items can have a random amount of makers.
I want to create both the Item and the MakerItem objects at the same time with a single set of data,
for example if a Maker with id = "abcd" already exists, and I go to /item and send a POST request with the following data:
{
"name": "item1",
"makers": [
{
"maker_id": "abcd"
}
]
}
I want the serializer to create the Item object and the MakerItem object.
I have achieved this, with the following setup:
views.py
class ItemListCreate(ListCreateAPIView):
queryset = Item.objects.all()
serializer_class = ItemSerializer
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class MakerItemSerializer(serializers.ModelSerializer):
class Meta:
model = MakerItem
exclude = ['id', 'item_id']
makers = MakerItemSerializer(many=True)
class Meta:
model = Item
fields = ['id', 'name', 'makers']
def create(self, validated_data):
maker_item_data = validated_data.pop('makers')
item_instance = Item.objects.create(**validated_data)
for each in maker_item_data:
MakerItem.objects.create(
item_id=check_instance,
maker_id=each['maker_id']
)
return item_instance
but when Django tries to return the created object, it always gives me the error:
AttributeError at /item/
Got AttributeError when attempting to get a value for field `makers` on serializer `ItemSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Item` instance.
Original exception text was: 'Item' object has no attribute 'makers'.
What am I doing wrong?
Thanks
EDIT: To clarify, the objects get created and populate the database correctly, but when the browsable API that DRF provides tries to display the created object, it gives me the error above.
Change:
class ItemSerializer(serializers.ModelSerializer):
class MakerItemSerializer(serializers.ModelSerializer):
class Meta:
model = MakerItem
exclude = ['id', 'item_id']
makers = MakerItemSerializer(many=True)
To:
class ItemSerializer(serializers.ModelSerializer):
class MakerItemSerializer(serializers.ModelSerializer):
class Meta:
model = MakerItem
exclude = ['id', 'item_id']
makers = MakerItemSerializer(many=True, source="makeritem_set")
Hope this works!
For clarity, you're attempting to serialise the reverse relationship between MakerItem and Item for this serialiser.
This means that the attribute on your object is automatically set by Django as fieldname_set but you can override this behaviour by setting the related_name kwarg on the field and then makemigrations and migrate it.
In your case you would need to do:
maker_id = models.ForeignKey(Maker, on_delete=models.CASCADE, related_name="maker_items")
And then update the field in the Meta to match the new field name, this way you don't have to manually specify source. Because actually the attribute "makers" is misleading, due to the fact its actually the MakerItem, not the Maker itself.
See https://docs.djangoproject.com/en/3.2/ref/models/relations/ for further details about this behaviour.

Serializer for fetchiing data from multiple classes

Environment is Python and Django3
I want to make api which retrieve the data from multiple model class.
I have models like this , each CountryStat has Country.
class Country(models.Model):
code = models.CharField(max_length=3,unique=True)
name = models.CharField(max_length=50)
class CountryStat((models.Model):
country = models.ForeignKey(Country, on_delete=models.CASCADE,null=True)
date = models.DateField(null=True,blank =True)
stat = models.IntegerField()
Now I want to get the latest Coutry Stat for each Country.
So I made the serializer for Country
class CountrySerializer(serializers.ModelSerializer):
latest_stat = serializers.SerializerMethodField()
class Meta:
model = Country
fields = ('id','code','latest_stat')
def get_latest_stat(self,obj):
# how can I get the latest stat from CountryStat model ????
Is this the correct idea or how can I make it??
You should define a custom latest_stat attribute on your model:
class Country(models.Model):
code = models.CharField(max_length=3,unique=True)
name = models.CharField(max_length=50)
def latest_stat(self):
return self.countrystat_set.order_by('-date').first()

Not sure I understand dependancy between 2 django models

I am struggling to understand django models relationship.
I have this arborescence:
A train have cars, and those cars are divided into parts. Then those parts all contains different references.
Like, for exemple, all the trains have the 6 cars, and the cars 6 parts. Each part have x reference to be associated.
I would like to use all of them in a template later on, where the user can select the train, the car and the part he worked on, then generate a table from his selections with only the references associated to the parts he selected.
It should update the train and the car (I'm trying to update a stock of elements for a company)
I dont really understand which model field give to each of them. After checking the doc, Ive done something like this but i am not convinced:
class Train(Car):
train = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
class Meta:
abstract = True
class Car(Part):
car = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
class Meta:
abstract = True
class Part(Reference):
part = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
class Meta:
abstract = True
class Reference(models.Model):
reference = models.CharField(max_length=200)
id = models.CharField(primary_key='True', max_length=100)
selected = models.BooleanField()
def __str__(self):
return self.reference
Can someone please help me understand this so I can do well ? Thanks!!
1-)if you add abstract = True in your Model Meta class, your class doesn't created on database as a table. If you store data for any class, you mustn't define abstract = True.
2-)For relations, you can use models.ForeignKey . If you add a class into brackets of another class, it names: inheritance.(You can think like parent-child relation). In database management, we can use foreignkey for one-to-many relationship.
3-)In Django ORM, id field automatically generated. So you don't need to define id field.
If I understand correctly, also you want to store parts of user's selected.
So, your model can be like that:
class Train(models.Model):
name = models.CharField(max_length=200) # I think you want to save name of train
class Car(models.Model):
train = models.ForeignKey(Train,on_delete=models.Cascade)
name = models.CharField(max_length=200)
class Part(models.Model):
car = models.ForeignKey(Car,on_delete=models.Cascade)
name = models.CharField(max_length=200)
class Reference(models.Model):
part = models.ForeignKey(Part,on_delete=models.Cascade)
name = models.CharField(max_length=200)
def __str__(self):
return self.reference
#addtional table for storing user's references
class UserReference(models.Model):
user = models.ForeignKey(User,on_delete=models.Cascade)
reference = models.ForeignKey(Reference,on_delete=models.Cascade)
name = models.CharField(max_length=200)
With this definitions, you can store user's definition on UserReference table. And with Django Orm, you can access train object from UserReferenceObject.
#user_reference: UserReference object like that result of UserReference.objects.first()
user_reference.reference.part.car.train.name

Parametrize a Python class

Is there any way to parametrize a class in Python? The parametrized class may look something like this:
class FIELDSerializer:
FIELD = serializers.CharField(source='get_FIELD_display', required=False)
class Meta:
model = None
fields = {FIELD}
Which would need to create the following three classes:
class NameSerializer:
name = serializers.CharField(source='get_name_display', required=False)
class Meta:
model = None
fields = {'name'}
class CategorySerializer:
category = serializers.CharField(source='get_category_display', required=False)
class Meta:
model = None
fields = {'category'}
class StateSerializer:
state = serializers.CharField(source='get_state_display', required=False)
class Meta:
model = None
fields = {'state'}
Is this possible or not?
You can do what you want with a factory function, although it's not completely trivial to get the internal variables (attributes) as you want them:
def factory(FIELDname):
class FIELDSerializer:
class Meta:
model = None
fields = {FIELDname}
settatr(FIELDSerializer, FIELDname, serializers.CharField(source=f'get_{FIELDname}_display', required=False))
return FIELDSerializer
CategorySerializer = factory('category')
StateSerializer = factory('state')
NameSerializer = factory('name')
The setattr allows us to set the name of attribute to the FIELDname string. (Thanks to #Code-Apprentice and #juanpa.arrivillaga for this idea.)
I don't know if there's any easy way to avoid the repetition of the field name and the desired class name when you call the factory without using something like exec (which is perfectly legal but usually leaves programmers with a bad taste in their mouths).

Categories