Random model values inside list subfactory elements - python

I cannot find an answer to my question. The thing is that I want to generate an User factory model where inside will be a subfactory List with Addresses. Each Addresses element must have different/random values (I mean that each element has non-repeatable unique values).
In my current implementation, all of the elements have the same values (maybe seeding is necessary?)
Actual code:
from pydantic import BaseModel
from factory import Factory, List, Subfactory
class Address(BaseModel):
Name: str
class User(BaseModel):
Addresses: list[Address]
class AddressFactory(Factory):
Name = fake.name()
class Meta:
model = Address
class UserFactory(Factory):
Addresses = List([SubFactory(AddressFactory) for _ in range(3)])
class Meta:
model = User
Actual output:
> UserFactory()
> User(Addresses=[Address(Name='Isa Merkx'), Address(Name='Isa Merkx'), Address(Name='Isa Merkx')])
Desired Output:
> UserFactory()
> User(Addresses=[Address(Name='Isa Merkx'), Address(Name='John Smith'), Address(Name='Elon Musk')])

In the code you provided, you have to replace fake.name() by Faker("name"), where Faker is imported from factory at the beginning of your file.
Indeed, fake.name() is called only once, when the class is defined, whereas you want it to happen each time you instantiate a factory.
You'll find a more detailed answer to your question here.

You should use the LazyAttribute to get a different value each time.
from typing import List
from pydantic import BaseModel
import factory
from faker import Faker
fake = Faker('en_GB') # <-- missing from the original example
class Address(BaseModel):
Street: str
HouseNumber: str
City: str
Postcode: str
class AddressFactory(factory.Factory):
Street = factory.LazyAttribute(lambda _: fake.street_name()) # <-- Lazy load the attribute values
HouseNumber = factory.LazyAttribute(lambda _: fake.building_number())
City = factory.LazyAttribute(lambda _: fake.city())
Postcode = factory.LazyAttribute(lambda _: fake.postcode())
class Meta:
model = Address
class User(BaseModel):
Addresses: List[Address]
class UserFactory(factory.Factory):
Addresses = factory.List([factory.SubFactory(AddressFactory) for _ in range(3)])
class Meta:
model = User
user = UserFactory()
user
The output:
User(Addresses=[Address(Street='Jade rapids', HouseNumber='3', City='Vanessaville', Postcode='B6H 2XA'), Address(Street='Wendy grove', HouseNumber='76', City='West Patricia', Postcode='WR5 0GL'), Address(Street='Smith ramp', HouseNumber='3', City='New Leslie', Postcode='L6 6JF')])

Related

Passing parameters through factory-boy Factory to SubFactory without specifying it

I'm using the pythons factory_boy package to create instances of models for testing purposes.
I want to pass the parameters used when calling Facotry.create() to all the SubFactories in the Factory being called.
Here's how I do it now:
Example 1:
I have to explicitly set the company when calling the SubFactory (the BagFactory)
class BagTrackerFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
bag = factory.SubFactory(BagFactory, company=factory.SelfAttribute("..company"))
class BagFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
Example 2:
In this example, I have to add company to Params in the BagFactory, so I can pass it down to ItemFactory which has the company parameter.
class BagTrackerFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
bag = factory.SubFactory(BagFactory, company=factory.SelfAttribute("..company"))
class BagFactory(BaseFactory):
item = factory.SubFactory(ItemFactory, company=factory.SelfAttribute("..company"))
class Params:
company = factory.SubFactory(CompanyFactory)
class ItemFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
The reason why I do it like this is that it saves time and it makes sense that the Bag belongs to the same company as the BagTracker when created by the same Factory.
Note: the BaseFactory is factory.alchemy.SQLAlchemyModelFactory
Question:
What I would like is to have company (and all the other parameters) from the parent Factory be passed down to SubFactories without having to pass it explicitly. And this continues downstream all the way to the last SubFactory, so every model has the same company, from the topmost parent Factory to the lowest child SubFactory. I hope you understand what I'm saying.
Is there an easy way to do this? Like some option in the factory-boy package?
EDIT:
I ended up doing it the long way, passing down parameters manually. In this example, I'm showing both cases: when the parent factory has the company parameter(BagTrackerFactory) and doesn't have it but must pass it downstream (BagFactory).
class CompanyFactory(BaseFactory):
id = get_sequence()
class Meta:
model = Company
class ItemFactory(BaseFactory):
id = get_sequence()
owner = factory.SubFactory(CompanyFactory)
owner_id = factory.SelfAttribute("owner.id")
class Meta:
model = Item
class BagFactory(BaseFactory):
id = get_sequence()
item = factory.SubFactory(ItemFactory, owner=factory.SelfAttribute("..company"))
item_id = factory.SelfAttribute("item.id")
class Params:
company = factory.SubFactory(CompanyFactory)
class Meta:
model = Bag
class BagTrackerFactory(BaseFactory):
id = get_sequence()
company = factory.SubFactory(CompanyFactory)
company_id = factory.SelfAttribute("company.id")
item = factory.SubFactory(ItemFactory, owner=factory.SelfAttribute("..company"))
item_id = factory.SelfAttribute("item.id")
bag = factory.SubFactory(BagFactory, company=factory.SelfAttribute("..company"))
bag_id = factory.SelfAttribute("bag.id")
class Meta:
model = BagTracker
This is possible, but will have to be done specifically for your codebase.
At its core, a factory has no knowledge of your models' specific structure, hence can't forward the company field — for instance, some models might not accept that field in their __init__, and providing the field would crash.
However, if you've got a chain where the field is always accepted, you may use the following pattern:
class WithCompanyFactory(factory.BaseFactory):
class Meta:
abstract = True
company = factory.Maybe(
"factory_parent", # Is there a parent factory?
yes_declaration=factory.SelfAttribute("..company"),
no_declaration=factory.SubFactory(CompanyFactory),
)
This works thanks to the factory_parent attribute of the stub used when building a factory's parameters: https://factoryboy.readthedocs.io/en/stable/reference.html#parents
This field either points to the parent (when the current factory is called as a SubFactory), or to None. With a factory.Maybe, we can copy the value through a factory.SelfAttribue when a parent is defined, and instantiate a new value.
This can be used afterwards in your code:
class ItemFactory(WithCompanyFactory):
pass
class BagFactory(WithCompanyFactory):
item = factory.SubFactory(ItemFactory)
class BagTrackerFactory(WithCompanyFactory):
bag = factory.SubFactory(BagFactory)
>>> tracker = BagTrackerFactory()
>>> assert tracker.company == tracker.bag.company == tracker.bag.item.company
... True
# It also works starting anywhere in the chain:
>>> company = Company(...)
>>> bag = BagFactory(company=company)
>>> assert bag.company == bag.item.company == company
... True
If some models must pass the company value to their subfactories, but without a company field themselves, you may also split the special WithCompanyFactory into two classes: WithCompanyFieldFactory and CompanyPassThroughFactory:
class WithCompanyFieldFactory(factory.Factory):
"""Automatically fill this model's `company` from the parent factory."""
class Meta:
abstract = True
company = factory.Maybe(
"factory_parent", # Is there a parent factory?
yes_declaration=factory.SelfAttribute("..company"),
no_declaration=factory.SubFactory(CompanyFactory),
)
class CompanyPassThroughFactory(factory.Factory):
"""Expose the parent model's `company` field to subfactories declared in this factory."""
class Meta:
abstract = True
class Params:
company = factory.Maybe(
"factory_parent", # Is there a parent factory?
yes_declaration=factory.SelfAttribute("..company"),
no_declaration=factory.SubFactory(CompanyFactory),
)

Can not create multiple instance of Model using Factory Boy and Faker

I am trying to craate multiple instance of django model using django-factory-boy and faker. But the I need to create instance in bulk (not single). But I can not make both attribute to be corresponding (code and name of currency).
I have a django model as:
class Currency(models.Model):
"""Currency model"""
name = models.CharField(max_length=120, null=False,
blank=False, unique=True)
code = models.CharField(max_length=3, null=False, blank=False, unique=True)
symbol = models.CharField(max_length=5, null=False,
blank=False, default='$')
def __str__(self) -> str:
return self.code
I have a factory
import factory
from apps.Payment.models import Transaction, Currency
from faker import Faker
fake = Faker()
class CurrencyFactory(factory.django.DjangoModelFactory):
class Meta:
model = Currency
# code and name get assigned when the class is called hence if we use
# create_batch(n) we get all n object same
# code, name = fake.currency()
code = factory.LazyAttribute(lambda _: fake.currency()[0])
name = factory.LazyAttribute(lambda _: fake.currency()[1])
symbol = '$'
The problem I am facing is code and name gets different value and not matching. See what faker returns.
>>> from faker import Faker
>>> fake = Faker()
>>> fake.currency()
('JPY', 'Japanese yen')
See currency name is not corresponding to currency code. Also I need to create at least 5 to 6 object using CurrencyFactory.create_batch(5).
# mismatch in code and name
NAME CODE
Netherlands Antillean guilder ZAR
Western Krahn language UGX
Colombian peso KHR
What I want
NAME CODE
Indian National Rupee INR
Japanese yen JPY
The best way would be to go through class Params:
class CurrencyFactory(factory.model.DjangoModelFactory):
class Meta:
model = Currency
class Params:
currency = factory.Faker("currency") # (code, name)
code = factory.LazyAttribute(lambda o: o.currency[0])
name = factory.LazyAttribute(lambda o: o.currency[1])
The idea is:
The factory calls Faker once, to generate the currency = (code, name) parameter;
It then maps the components of that parameter to the right fields for your model
Since currency is declared as a parameter, it won't be passed to the model (it's automatically added to Meta.exclude.

Display custom format name on django admin page

I want to display first name and last name. To achieve that, I have used __str__() method. But it is not working.
class OnlineUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return f"{self.user.first_name} {self.user.last_name}"
Instead it display, user ID.
In admin.py,
class OnlineUsersAdmin(admin.ModelAdmin):
# a list of displayed columns name.
list_display=['user']
admin.site.register(OnlineUsers, OnlineUsersAdmin)
Where I'm doing wrong? How to get the format I want?
Version:
Django==2.0.1
Debug:
user_info=OnlineUsers.objects.get_or_create(user=self.user)
print(user_info.__str__())
Output:
((<OnlineUsers: FIRST_NAME LAST_NAME >, False))
You should use __str__ instead of user in list_display:
list_display=['__str__']
Otherwise you tell django to show user field. And since User model doen't have overrided __str__ method you see user's id.
Also you can just remove list_display attribute. In this case __str__ will be used by default.
The list_display--(Django doc) support callable methods too. So, define a user(...) method on Model admin as,
class OnlineUsersAdmin(admin.ModelAdmin):
# a list of displayed columns name.
list_display = ['user']
def user(self, instance):
return instance.__str__()
Here, inside return you can return only str + str, not str + int. So, you have to convert every int to str
from django.db import models
# Create your models here.
class Fabonacci(models.Model):
numstr = models.IntegerField()
terms = models.CharField(max_length=500)
def __str__(self):
return "The first " + str(self.numstr) + " terms in fibonacci series : " + self.terms

How can I add a field's length for the sort in Model?

How can I add a field's length for the sort in Model?
I have an IPv4Manage model:
class IPv4Manage(models.Model):
"""
ipv4
"""
ip = models.GenericIPAddressField(help_text="ip")
...
class Meta:
ordering = ['ip']
In the Meta we can set the ordering criteria with the fields.
But I have a question, can I set the ip's length for order in the Model?
I know if in the APIView I can use Prefetch or extra for the length of the ip and order it.
...extra(select={'length':'Length(ip)'}).order_by('length', 'ip')
But can we set the length of field param of order in the Model?
EDIT-1
I created a Manager:
class IPv4ManageManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(length=Length('ip')).order_by('length', 'ip')
and I add it to the default Manager, it works for me.
class IPv4Manage(models.Model):
"""
ipv4
"""
ip = models.GenericIPAddressField(help_text="ip")
...
objects = IPv4ManageManager()
class Meta:
ordering = ['ip']
In Django 2.0 you can use query expresions with ordering:
from django.db.models.functions import Length
class IPv4Manage(models.Model):
"""
ipv4
"""
ip = models.GenericIPAddressField(help_text="ip")
...
class Meta:
ordering = [Length('ip').asc()]

Take JSONAPI schema from model

In my Rest application I want to return json like JSONAPI format, but I need to create Schema class for it and create every field again that are already there in my model. So instead of creating every field in schema class can I not take it from DB Model..
below is my model class
class Author(db.Model):
id = db.Column(db.Integer)
name = db.Column(db.String(255))
I am defining Schema like below.
class AuthorSchema(Schema):
id = fields.Str(dump_only=True)
name = fields.Str()
metadata = fields.Meta()
class Meta:
type_ = 'people'
strict = True
So here, id and name I have defined it twice. so is there any option in marshmallow-jsonapi to assign model name in schema class so it can take all fields from model
Note: I am using marshmallow-jsonapifor it, I have tried marshmallow-sqlalchemy , it has that option but it not return json in JSONAPI format
You can use flask-marshmallow's ModelSchema and marshmallow-sqlalchemy in combination with marshmallow-jsonapi with the caveat that you have to subclass not only the Schema classes but also the SchemaOpts classes, like this:
# ...
from flask_marshmallow import Marshmallow
from marshmallow_jsonapi import Schema, SchemaOpts
from marshmallow_sqlalchemy import ModelSchemaOpts
# ...
ma = Marshmallow(app)
# ...
class JSONAPIModelSchemaOpts(ModelSchemaOpts, SchemaOpts):
pass
class AuthorSchema(ma.ModelSchema, Schema):
OPTIONS_CLASS = JSONAPIModelSchemaOpts
class Meta:
type_ = 'people'
strict = True
model = Author
# ...
foo = AuthorSchema()
bar = foo.dump(query_results).data # This will be in JSONAPI format including every field in the model

Categories