How can I combine/nest EnumFields in Django? - python

Django-EnumFields lets you combine Enum fields in Django (the clue was in the title).
Can you combine nest these?
Here's an example that plays off the docs:
from django.db import models
from django_enumfield import enum
class BeerStyle(enum.Enum):
LAGER = 0
STOUT = 1
WEISSBIER = 2
class SoftDrinkStyle(enum.Enum):
COKE = 3
LEMONADE = 4
class Drink(models.Model):
style = enum.EnumField(????, default=BeerStyle.LAGER)
I don't know what would go in place of ????, or if there is a better way to get this nested/combination to play out with Django. I'm mainly asking as I want Enum behaviour, with the ability to probe different types, e.g. in a save method, check for User age if the Drink is or type Beer.
Is this possible? Having played with this for a bit I don't see how.

Having looked over how Python Enums work, this looks like the best behaviour to mock up 'subclasses'
from django.db import models
from django_enumfield import enum
class DrinkStyle(enum.Enum):
LAGER = (0, 'Beer')
STOUT = (1, 'Beer')
WEISSBIER = (2, 'Beer')
COKE = (3, 'SoftDrink')
LEMONADE = (4, 'SoftDrink')
def __init__(self, id, drink_type):
self.id = id
self.type = drink_type
#property
def type(self):
return self.drink_type
class Drink(models.Model):
style = enum.EnumField(DrinkStyle, default=DrinkStyle.LAGER)
Then use DrinkStyle.COKE.type to return the type.

Related

Best way to represent a "doubled" enum value

I'm primarily a C++ developer, but I fairly frequently end up writing Python scripts. I'm currently writing a dice simulator for a game, and I'm not certain of the best way, in Python, the solve my problem.
There are three player skills, and each player is strong at one, medium at one, and weak at one. I've written some classes that calculate the faces of the die from each of the player skils.
The skills live in an enum, rather than writing strings all over the place. However, there's also a chance that the skill will be "doubled", and become stronger.
Given that I am returning a list of Skill enum element, what is the best way to indicate a skill has been doubled?
Things I've considered so far:
Extending the Skill Enum to include doubled skills, but that makes the mapping of player skills to faces harder
Creating a DoubleSkill Enum, but then any changes to the Skill Enum would need to be replicated in DoubleSkill
My preferred option, which is creating a wrapper DoubleSkill class that can be constructed from a Skill. However, then it isn't of type enum, which makes my strongly-typed C++ instincts nervous
import random
from abc import ABC, abstractmethod
from enum import Enum
from collections import namedtuple
class Skill(Enum):
Might = 1,
Wisdom = 2,
Cunning = 3
class Die(ABC):
#abstractmethod
def _faces(self):
'''Returns the faces of this die'''
pass
def roll(self):
'''Returns a random face'''
return random.choice(self._faces())
PlayerSkills = namedtuple("PlayerSkills", ("strong", "medium", "weak"))
class PlayerDie(Die):
#abstractmethod
def _skills(self):
'''Returns the characer's skills'''
pass
def _faces(self):
'''Work out the faces of the die based off the skills'''
skills = self._skills()
#I want this to return a representation of the skill, not a string.
#But then I'd be mixing Enums and not-Enums
return [
self._double(skills.strong),
self._double(skills.medium),
skills.strong.name,
skills.strong.name,
skills.medium.name,
skills.weak.name
]
def _double(self, skill):
return f"Double {skill.name} (block)"
class CookDie(PlayerDie):
def _skills(self):
return PlayerSkills(Skill.Might, Skill.Cunning, Skill.Wisdom)
print(CookDie().roll())
One possible way is to make your enumration a Flag instead:
from enum import Flag, auto
class Skill(Flag):
Might = auto()
Wisdom = auto()
Cunning = auto()
Doubled = auto()
and in use:
>>> for i in (1, 2, 4, 9, 10, 12):
... i, Skill(i)
(1, <Skill.Might: 1>)
(2, <Skill.Wisdom: 2>)
(4, <Skill.Cunning: 4>)
(9, <Skill.Doubled|Might: 9>)
(10, <Skill.Doubled|Wisdom: 10>)
(12, <Skill.Doubled|Cunning: 12>)
If you would like a prettier str() (or one at all for a combined member), you can add your own __str__:
class Skill(Flag):
Might = auto()
Wisdom = auto()
Cunning = auto()
Doubled = auto()
#
def __str__(self):
cls = self.__class__
cls_name = cls.__name__
doubled = (self & cls.Doubled or "") and " x2"
base = (self & ~cls.Doubled) or self
name = base.name
if name is None:
name = '|'.join(s.name for s in Skill if s & base)
return "%s.%s%s" % (cls_name, name, doubled)
and in use:
for i in (1, 2, 4, 9, 10, 12):
i, str(Skill(i))
(1, 'Skill.Might')
(2, 'Skill.Wisdom')
(4, 'Skill.Cunning')
(9, 'Skill.Might x2')
(10, 'Skill.Wisdom x2')
(12, 'Skill.Cunning x2')

Python 3 Scope between classes in separate files?

I have been researching for ages and cannot find this specific question being asked (so perhaps I am missing something simple!) but I have had trouble separating classes into different .py files.
Scenario:
Main class imports a Settings class file and a Work class file..Settings class populates a list with objects instantiated from an Object class file...
Work class wants to cycle through that list and change values within each of those objects. <-- here is where I come unstuck.
I have tried it by making the values class variables rather than instance. Still I have to import the settings class in the work class in order to write the code to access the value to change. But it wont change the instance of that class within the main class where all these classes are called!
I read an article on Properties. The examples they gave were still examples of different classes within the same file.
Any advice as to what I should be looking at would be greatly appreciated!
This is what I was doing to test it out:
Main File where all will be run from:
import Set_Test
import Test_Code
sting = Set_Test.Settings()
tc = Test_Code.Testy()
ID = sting._settingsID
print(f'Settings ID is: {ID}')
tc.changeVal()
ID = sting._settingsID
print(f'Settings ID is: {ID}')
Set_Test.py:
class Settings:
def __init__(self):
self._settingsID = 1
#property
def settingsID(self):
return self._settingsID
#settingsID.setter
def settingsID(self, value):
self.settingsID = value
Test_Code.py:
import Set_Test
class Testy:
def changeVal(self):
Set_Test.Settings.settingsID = 8
Thanks to stovfl who provided the answer in comments. I managed to decipher what stovfl meant eventually :D
I think!
Well the below code works for anyone who wants to know:
Main:
import Set_Test
import Test_Code
sting = Set_Test.Settings()
tc = Test_Code.Testy()
ID = sting._settingsID
print(f'Settings ID is: {ID}')
tc.changeVal(sting)
ID = sting._settingsID
print(f'Settings ID is: {ID}')
Set_Test.py:
class Settings:
def __init__(self):
self._settingsID = 1
#property
def settingsID(self):
return self._settingsID
#settingsID.setter
def settingsID(self, value):
self._settingsID = value
Test_Code.py
import Set_Test
sting = Set_Test.Settings()
class Testy():
def changeVal(self, sting):
print(sting.settingsID)
sting.settingsID = 8
print(sting.settingsID)

Pass enum type as constructor argument

I am new to python and I would like to pass an enum as an argument to a constructor, within a function.
EDIT: I am working on a program with a class that has to organize different types of data, but most of these data types can be treated the same way. This data won't be all be added at the same time or in a foreseeable order. I would therefore like to keep the same functions, and just change the way the constructor stores the data. Let's consider this simpler example:
Say I have an enum
from enum import Enum, auto
class HouseThing(Enum):
people = auto()
pets = auto()
furniture = auto()
And I have a class House that can contain some or all of those things
class House():
def __init__(self, address, people = None, pets = None,
furniture = None):
self.address = address,
if self.people is not None:
self.people = people
etc....
And now I want to have a function that makes new furbished houses, but I want to use a function that could be used for any house:
house_things = HouseThing.furniture
def make_house_with_some_house_things(neighborhood, house_things):
neighborhood.append(House(house_things.name = house_things.name))
Is there a way to do this without first testing what kind of HouseThing house_things is first? house_things.name passes a string, but I would like it to be able to use it as a keyword.
I'm not sure exactly what you are trying to achieve here, but for the sake of solving the puzzle:
First, change House to determine what it has been passed:
class House():
def __init__(self, address, *house_things):
self.address = address
for ht in house_things:
if ht is HouseThings.people:
self.people = ht
elif ht is HouseThings.pets:
self.pets = ht
elif ht is HouseThings.furniture:
self.furniture = ht
else:
raise ValueError('unknown house thing: %r' % (ht, ))
Then, change make_house_with_some_house_things to just pass the house things it was given:
def make_house_with_some_house_things(neighborhood, house_things):
neighborhood.append(House(house_things))

How to pass in a starting sequence number to a Django factory_boy factory?

factory_boy defaults to 1 for sequences. How can I pass in a number to use as a different starting number instead? I can subclass the _setup_next_sequence() method, but how can I give it a variable to use?
# File: models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
# File: factories.py
from .models import Book
import factory
class BookFactory(factory.Factory):
FACTORY_FOR = BookModel
title = factory.Sequence(lambda n: u'Title #{}'.format(n))
#classmethod
def _setup_next_sequence(cls):
# Instead of defaulting to starting with number 1, start with starting_seq_num.
# But how do I set starting_seq_num?
return starting_seq_num
# File: make_data.py
from factories import BookFactory
# somehow set starting sequence number here?
BookFactory().create()
I'm using factory_boy 1.2.0 (via pip install factory_boy)
factory_boy code: https://github.com/dnerdy/factory_boy
In addition to the answer of Rob Bednark
We can use reset_sequence() function, which will reset the counter to a specific value.
# File: make_data.py
import factories
factories.BookFactory.reset_sequence(100)
my_book = factories.BookFactory().create()
print(my_book.title) # Title #100
I found two ways of solving this:
Use a module variable
Use a class attribute set outside of the class definition
Use a module variable:
# File: factories.py
from .models import Book
import factory
starting_seq_num = 0
class BookFactory(factory.Factory):
FACTORY_FOR = BookModel
title = factory.Sequence(lambda n: u'Title #{}'.format(n))
#classmethod
def _setup_next_sequence(cls):
# Instead of defaulting to starting with 0, start with starting_seq_num.
return starting_seq_num
# File: make_data.py
import factories
factories.starting_seq_num = 100
factories.BookFactory().create()
Use a class attribute set outside of the class definition:
# File: factories.py
from .models import Book
import factory
class BookFactory(factory.Factory):
# Note that starting_seq_num cannot be set here in the class definition,
# because Factory will then pass it as a kwarg to the model's create() method
# and cause an exception. It must be set outside the class definition.
FACTORY_FOR = BookModel
title = factory.Sequence(lambda n: u'Title #{}'.format(n))
#classmethod
def _setup_next_sequence(cls):
return getattr(cls, 'starting_seq_num', 0)
# File: make_data.py
from factories import BookFactory
BookFactory.starting_seq_num = 100
BookFactory().create()
Update: factory_boy now handles it!
In the latest version of factory_boy (2.8.1 to this day) it is now possible to force the sequence counter into a define value:
Forcing the value on a per-call basis
In order to force the counter for a specific Factory instantiation, just pass the value in the
__sequence=42 parameter:
class AccountFactory(factory.Factory):
class Meta:
model = Account
uid = factory.Sequence(lambda n: n)
name = "Test"
Then in the console:
>>> obj1 = AccountFactory(name="John Doe", __sequence=10)
>>> obj1.uid # Taken from the __sequence counter
10
>>> obj2 = AccountFactory(name="Jane Doe")
>>> obj2.uid # The base sequence counter hasn't changed
1
And it is also possible to reset the counter to a specific value:
>>> AccountFactory.reset_sequence(42)
>>> AccountFactory().uid
42
>>> AccountFactory().uid
43
The third, and simplest way:
# File: factories.py
from .models import BookModel
import factory
class BookFactory(factory.Factory, starting_seq_num):
FACTORY_FOR = BookModel
title = factory.Sequence(lambda n: u'Title #{}'.format(n + starting_seq_num))
# File: make_data.py
import factories
book = factories.BookFactory(512).create() #Start with 512
I'm only starting with Factory Boy myself, and not too experienced in Python either, so I may be missing something, but you see where I'm going here. To make it clearer, I think I'd actually prefer it to be keyworded:
class BookFactory(factory.Factory, title_seq_start=-1):
...
book = factories.BookFactory(title_seq_start=512).create()

Matching 3 out 5 fields - Django

I'm finding this a bit tricky! Maybe someone can help me on this one
I have the following model:
class Unicorn(models.Model):
horn_length = models.IntegerField()
skin_color = models.CharField()
average_speed = models.IntegerField()
magical = models.BooleanField()
affinity = models.CharField()
I would like to search for all similar unicorns having at least 3 fields in common.
Is it too tricky? Or is it doable?
You should use Q objects. The rough example is:
from django.db.models import Q
from itertools import combinations
# this -- the unicorn to be matched with
attr = ['horn_length', 'skin_color', 'average_speed', 'magical', 'affinity']
q = None
for c in combinations(attrs, 3):
q_ = Q(**{c[0]: getattr(this, c[0])}) & Q(**{c[1]: getattr(this, c[1])}) & Q(**{c[2]: getattr(this, c[2])})
if q is None:
q = q_
else:
q = q | q_
Unicorn.objects.get(q)
not tested, though
It has to be done in the HAVING clause:
SELECT ... HAVING (IF(a.horn_length=b.horn_length, 1, 0) + ...) >= 3
There's no way to express HAVING in the Django ORM so you'll need to drop to raw SQL in order to perform it.
This should cover your question, if I understood it right:
from django.db import models
Unicorn.objects.filter(models.Q(skin_color = 'white') | models.Q(magical = True))
This would filter all unicorns that have skin color white or have some magical stuff in common. More about the Q objects here http://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects
I have never used Django and i'm rather novice in Python but perhaps you can do something like this:
make a method that compares two instances of the class Unicorn.
def similarity(self, another)
sim = 0
if (self.horn_length==another.horn_length):
sim+=1
if (self.skin_color==another.skin_color):
sim+=1
if (self.average_speed==another.average_speed):
sim+=1
if (self.magical==another.magical):
sim+=1
if (self.affinity==another.affinity):
sim+=1
return sim
Then you can test with something like:
myUnicorn
for x in unicornsList:
if myUnicorn.similarity(x) >=3:
...

Categories

Resources