Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide standalone access to UserFactory username generation #20

Open
k0nG opened this issue Nov 10, 2016 · 3 comments
Open

Provide standalone access to UserFactory username generation #20

k0nG opened this issue Nov 10, 2016 · 3 comments

Comments

@k0nG
Copy link

k0nG commented Nov 10, 2016

When using a custom user model I'd love to still use your unique username generation.

Would it be possible to provide the Username generator as it's own standalone module/provider?

Something like:

from factory.django import DjangoModelFactory
from factory_djoy import UniqueUsername

class AccountFactory(DjangoModelFactory):

    username = UniqueUsername()
@jamescooke
Copy link
Owner

jamescooke commented Sep 28, 2019

Had some time to think about this 😂

This week I've wished for a unique generator for strings when working with Factory Boy. I'm not satisfied with using Sequences to guarantee unique values - especially for stringy values which are not meant to contain numbers.

I've started work on uFaker to add functionality to Faker to make it easier to generate unique values and or exclude undesired ones.

Quick recap: factory_djoy does two things when generating user names for Django Users:

So, back to the original question:

Would it be possible to provide the Username generator as it's own standalone module/provider?

Do you care about values in the database already?

If no - then would ufake.user_name() work? My plan is to wire a ufake instance into factory_djoy so that unique values can be easily generated across multiple calls to factories.

If yes - then UniqueUsername() could be made available. My main concern would be that this would not be generic enough to be helpful - for example if the model field name or database column name change.

What I'm thinking of for the "yes I care about existing values in the database" situation is a recipe that uses a ban list:

from factory_djoy import CleanModelFactory, ufake

from project.accounts import Account

class AccountFactory(CleanModelFactory):
    class Meta:
        model = Account

    @lazy_attribute
    def username(self):
        all_usernames = Account.objects.values_list('username', flat=True)
        return ufake.user_name(ban=all_usernames)

What do you think? Would the recipe be powerful enough for what you're looking for?

Either way, my plan is to build out uFaker to the API in its README and then replace the username generation in this library with the recipe above.

@k0nG
Copy link
Author

k0nG commented Oct 15, 2019

I'm so glad that you got to this within 3 years, I was worried we'd go into the 4th year..... 😆

I understand your thinking and I think I do care about existing values in the database sometimes. So a ban list does not sound like a bad idea.

@jamescooke
Copy link
Owner

Just a note to self with an example for testing.

Given a Game model that keeps a single unique value pointing a Server FK on another system:

class Game(models.Model):
    server_id = models.IntegerField(unique=True)
    code = models.CharField(max_length=255)
    created_at = models.DateTimeField(auto_now=True)

Then we would have a factory (note the change of import name from the Username example above - this is so that the API matches Factory Boy):

from factory import Faker
from factory_djoy import CleanModelFactory, uFaker

from games.models import Game


class GameFactory(CleanModelFactory):
    class Meta:
        model = Game

    server_id = uFaker('pyint')  # This guarantees unique values for this ID
    code = Faker('word')  # We don't care about uniqueness here.

This would then mean that a large number of valid Game instances can be created without the server_id accidentally colliding and without using Factory Boy's sequence solution: GameFactory.create_batch(999).

It also means that we can easily test what happens when there are missing values without the chance of collision, but maintaining some randomness:

def test_missing():
    """
    When no games can be found, serializer is valid, but `_game` is `None`.
    """
    GameFactory.create_batch(3, server_id__ban=[29])
    serializer = GameSearchSerializer(data={'server_id': 29})

    result = serializer.is_valid()

    assert result is True, serializer.errors
    assert serializer._game is None

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants