Overwrite django choices output in graphene - python

I'm working with graphene and graphene-django and I have a problem with a IntegerField with choices. graphene create an Enum and the output is "A_1" if the value is 1; "A_2" if the value is 2 and so on. Example:
# model
class Foo(models.Model):
score = models.IntegerField(choices=((1, 1), (2, 2), (3, 3), (4, 4), (5, 5)))
# query
query {
foo {
score
}
}
# response
{
"data": {
"foo": {
"source": "A_1"
}
}
}
I found a function that convert the choices values.
def convert_choice_name(name):
name = to_const(force_text(name))
try:
assert_valid_name(name)
except AssertionError:
name = "A_%s" % name
return name
And assert_valid_name has this regular expression:
r'^[_a-zA-Z][_a-zA-Z0-9]*$'
Therefore, whatever begins with number, it will convert it to "A_...".
How I can overwrite this output?

The code comments say
GraphQL serializes Enum values as strings, however internally Enums
can be represented by any kind of type, often integers.
So for your particular case, you're not going to be able to replace the over-the-wire values with integers easily. But it may not matter if the actual value represented by the strings ("A_1") is still an integer internally and on the client end (from the field's description values.)
In general though you can replace the automatically generated field for the field with choices by defining an enum class and adding to the definition of the DjangoObjectType. Here's an example using the documentation Enum example...
class Episode(graphene.Enum):
NEWHOPE = 4
EMPIRE = 5
JEDI = 6
#property
def description(self):
if self == Episode.NEWHOPE:
return 'New Hope Episode'
return 'Other episode'
which you could then add to your DjangoObjectType like
class FooType(DjangoObjectType):
score = Episode()
class Meta:
model = Foo
Or if you want to get extra fancy you can generate the Enum field dynamically from your field's choices in Foo._meta.get_field('score').choices. See graphene_django.converter.convert_django_field_with_choices.

You can set convert_choices_to_enum to False in your Graphene-Django model which will leave them as integers.
class FooType(DjangoObjectType):
class Meta:
model = Foo
convert_choices_to_enum = False
There is more information on the setting here.

Having just bumped into this myself, an alternate is to define your field outside of the Meta (use only_fields) as just a graphene.Int , then you can provide your own resolver function and just return the value of the field, which will end up as a number.
My code snippet (the problem field was resource_type which is the Enum):
class ResourceItem(DjangoObjectType):
class Meta:
model = Resource
only_fields = (
"id",
"title",
"description",
"icon",
"target",
"session_reveal",
"metadata",
"payload",
)
resource_type = graphene.Int()
def resolve_resource_type(self, info):
return self.resource_type

Related

In Django, how do I define a string value for IntegerChoices enum?

I'm using Django 3.2 and Python 3.9. In my model, I define an int enum. I would also like to define readable string values for it, so I tried
class Transaction(models.Model):
class TransactionTypes(models.IntegerChoices):
BUY = 0
SELL = 1
labels = {
BUY: 'Buy',
SELL: 'Sell'
}
translation = {v: k for k, v in labels.items()}
but this definition fails with the error
TypeError: int() argument must be a string, a bytes-like object or a number, not 'dict'
How would I define strings for each value? I don't mind if the strings are just the literal variable names (e.g. "BUY", "SELL")
Edit: In response to the one of the answers given, seeing this result ...
>>> t = Transaction.objects.all().first()
>>> t.type
0
>>> str(t.type)
'0'
A simpler way to go about it as per django official documentation for Django3.2
class Transaction(models.Model):
class TransactionTypes(models.IntegerChoices):
BUY = 0, _('Buy')
SELL = 1, _('Sell')
(or)
class Transaction(models.Model):
class TransactionTypes(models.IntegerChoices):
BUY = 0, 'Buy'
SELL = 1, 'Sell'
Another way is to make use of Enum functional api, this is also mentioned in the Django 3.2 official documentation
TransactionTypes = models.IntegerChoices('TransactionTypes', 'BUY SELL')
TransactionTypes.choices
#provides below output
>>>[(1, 'Buy'), (2, 'Sell')]
EDIT: 1
Considering you only have a handful of transaction types (like Buy/Sell and other future transaction type possibilities like Exchange or Return), I would suggest to use PositiveSmallIntegerField which is more apt for your scenario.
Here PositiveSmallIntegerField supports values from 0 to 32767 compared to SmallIntegerField supports values from -32768 to 32767
SYNTAX:
models.PositiveSmallIntegerField(**Field Options)
Example:
class Transaction(models.Model):
class TransactionTypes(models.IntegerChoices):
BUY = 0, 'Buy'
SELL = 1, 'Sell'
start_transactionType= models.PositiveSmallIntegerField(choices=TransactionTypes.choices, default=TransactionTypes.BUY, help_text="Do you wish to Buy or Sell?", null=True, blank=True, primary_key=False, editable=True)
def __str__(self):
return '%s' % (self.start_transactionType)
__ str __ is a Python method that returns a string representation of any object. This is what Django uses to display model instances as a plain string.
Field Options
choices : Sets the choices for this field
default: The default value for the field
help_text: Extra “help” text to be displayed with the form widget. It’s useful for documentation even if your field isn’t used on a form
null: If set to True Django stores empty values as NULL in database, by default it is False.
blank: If True, this field is allowed to be blank, by default its False
primary_key: If True, this field is primary key for model, by default it is False
editable: If False, the field will not be displayed in the admin or any other ModelForm. They are also skipped during model validation. Default is True.
For a live example you can follow this 5 part tutorial series,
part 5: Fluent in Django: Get to know Django models better
EDIT: 2
A number of custom properties are added to the enumeration classes – .choices, .labels, .values, and .names – to make it easier to access lists of those separate parts of the enumeration.
As per django documentation use can the .label property or .name property
TransactionTypes.BUY.label
>>> “Buy” #returns this output as string value
TransactionType.BUY.name
>>> “BUY” # returns this output
TransactionType.BUY.value
>>> 0 # returns this as output
EDIT 3 Based on updated question &comments
Brief information covered in Edit 3
extra instance method example quoted from django 3.2 doc
How to apply extra instance method to your use case
Workaround function to solve issues
Django 3.2 documentation on extra instance method mentions
For every field that has choices set, the object will have a get_FOO_display() method, where FOO is the name of the field. This method returns the “human-readable” value of the field.
Sample example from documentation is given below
from django.db import models
class Person(models.Model):
SHIRT_SIZES = (
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES)
>>>p = Person(name="Fred Flintstone", shirt_size="L")
>>>p.save()
>>>p.shirt_size
‘L’ #output
>>> p.get_shirt_size_display()
‘Large’ #output
APPLYING extra instance method to your use case
Based on your updated question & comments you have mentioned t to be instance of Transactions object and type to be PositiveSmallIntegerField (an instance of TransactionTypes choices)
The t.get_type_display() code should ideally produce the output Buy as string
>>> type= models.PositiveSmallIntegersField(choices=TransactionTypes.choices, null=True, blank=True)
>>> t = Transaction.objects.all().first()
>>> t.type
0 #output
>>> t.get_type_display()
‘Buy’ #output
Workaround
A workaround is to write a separate function that checks with int enum value and return label as string
def label_of_TransactionType:
if (t.type== TransactionType.BUY.value):
return TransactionTypes.BUY.label
else:
return TransactionTypes.SELL.label
I appreciate the consistent definition, but there are other answer for this question is using enums and I think the Enum type is by far the best. They can show an integer and a string for an item at the same time, while keeping your code more readable.see the code snippets below:
app/enums.py
from enum import Enum
class ChoiceEnum(Enum):
def __str__(self):
return self.name
def __int__(self):
return self.value
#classmethod
def choices(cls):
choices = list()
for item in cls: # Loop thru defined enums
choices.append((item.value, item.name))
return tuple(choices)
class TransactionType(ChoiceEnum):
BUY = 0
SELL = 1
# Uh oh
TransactionType.BUY._name_ = 'Buy'
TransactionType.SELL._name_ = 'Sell'
app/models.py
from django.db import models
from myapp.enums import TransactionType
class Transaction(models.Model):
type_of_transaction = models.IntegerField(choices=TransactionType.choices(), default=int(TransactionType.BUY))
# ...
One way is that you define choices as a Tuple of Tuple, each option being the value of the outer tuple.
The first element in each inner tuple is the value to be set in the model, and the second element being its string representation.like the code snippet below:
class Transaction(models.Model):
BUY = 0
SELL = 1
TRANSACTION_TYPE_CHOICES = (
(BUY, 'Buy'),
(SELL, 'Sell'),
)
type_of_transaction = models.IntegerField(
choices=TRANSACTION_TYPE_CHOICES,
default=‌BUY,
)

Accessing the data of the parent graphene.ObjectType from the nested class

As mentioned
in this question, a nested query in graphene in python can be done. Is there a way to access the data of a parent query?
With reference to the same question
query {
person (id: "Mary") {
id
name
}
}
The nested query:
query {
people {
group
allPersons {
name
}
}
}
How would I access group from people in the resolver for allPersons.
The graphene documentation doesn't make this clear.
As the page mentioned in the question the root query is as follows:
class YourQuery(object):
# person = ...
people = graphene.Field(AllPeopleType)
def resolve_people(self, info):
return AllPeopleType()
To access the variables of the parent of Person which triggered resolve_all_persons, i.e AllPersons
class AllPeopleType(graphene.ObjectType):
group = graphene.Int()
all_persons = graphene.List(Person)
def resolve_all_persons(self, info, **kwargs):
# This allows us to access group of AllPersons
personGroup = self['group']
return Person.objects.all()
However this will not work if all_persons resolver is defined as
all_persons = graphene.List(Person, resolver = yourResolverFunctionHere)
This is becuase the resolver for Person (and anything within AllPersons) must be a member of the Parent class AllPersons which resolves its own variables.

How to use factory.LazyAttribute with Faker() functions

I am using factory_boy to build some fixtures in Django.
I want to use factory.LazyAttribute to build one attribute based on the condition of another attribute.
class MyFactory(factory.django.DjangoModelFactory):
class Meta:
model = MyModel
title = 'My Title' if random() < 0.5 else None
description = factory.LazyAttribute(
lambda a: factory.Faker(
'paragraph', nb_sentences=1, variable_nb_sentences=False
) if a.title else None)
However, this returns a string being <factory.faker.Faker object at 0x000001B10597BB20> rather than executing the correct paragraph generation.
Where am I going wrong?
factory.Faker is a special object: when a factory instantiates an object, it will ask the factory.Faker proxy to get a random value from faker.
The simplest way to achieve what you're looking for is to use factory.Maybe:
class MyFactory(factory.django.DjangoModelFactory):
class Meta:
model = MyModel
title = factory.fuzzy.FuzzyChoice(["My Title", None])
description = factory.Maybe('title', factory.Faker('paragraph'))
Note that, in the code you shared, the title = "My title" if random() < 0.5 else None is computed exactly once, when Python parses the file. I've used factory.fuzzy.FuzzyChoice to have that random computation performed for each object. This function also uses factory_boy's randomness management features.
Another option would be to use parameters (class Params):
class MyFactory(factory.django.DjangoModelFactory):
class Meta:
model = MyModel
class Params:
# Items here will be available to further declarations, but won't be
# passed to the model constructor.
description_contents = factory.Faker('paragraph')
title = factory.fuzzy.FuzzyChoice(["My Title", None])
description = factory.LazyAttribute(lambda a: a.description_contents if a.title else None)
Or if you need a more specific interaction with the Faker library:
from faker import Faker as RealFaker
real_faker = RealFaker()
Inside the factory:
factory.LazyAttribute(lambda a: real_faker.email())

How should I represent a bit flags int field in django admin?

I have a data model with a bitfield defined something like this:
alter table MemberFlags add column title varchar(50) not null default '';
alter table MemberFlags add column value integer( 3) not null default 0;
insert into MemberFlags (title, value) values
("Blacklisted", 1),
("Special Guest", 2),
("Attend Ad-hoc Sessions", 4),
("Attend VIP Sessions", 8),
("Access Facility A", 16),
("Access Facility B", 32)
And used like this:
alter table Membership add column title varchar(50) not null default '';
alter table Membership add column flags integer( 3) not null default 0;
insert into Membership (title, flags) values
("Guest Pass", 4+2 ),
("Silver Plan", 16+ 4 ),
("Gold Plan", 32+16+ 4+2 ),
("VIP Pass", 32+16+8+4+2 )
My questions are:
A) What's the easiest way to represent the different bitflags as separate items in the admin site? Should I override the template, or do something with forms?
B) How about the search list? I could create functions in the model to represent each bit, but how would searching and sorting be done?
I'm new to Django.
A great tested solution, even if it doesn't fit your model right away, would be using django-bitfield
I think the best solution here would be for you to create a new field type by subclassing models.Field. You could make use of the choices parameter to assign the valid bit flags and their meanings. This would help keep your model declaration clean and readable, with a final result along the lines of:
class BitFlagField(models.Field):
...
class MyModel(models.Model):
...
FLAG_CHOICES = (
(1, 'Blacklisted'),
(2, 'Special Guest'),
(4, 'Attend Ad-hoc Sessions'),
(8, 'Attend VIP Sessions'),
(16, 'Access Facility A'),
(32, 'Access Facility B'),
)
flags = BitFlagField(choices=FLAG_CHOICES)
...
The Django documentation has a great in-depth article on how to go about subclassing models.Field:
Writing Custom Model Fields
It seems to cover everything you need to do, including:
Specifying Form Field (Tying a form to the field so that django-admin knows how to display it.)
Preparing Values for Lookups (Which will allow you to use the field for searches and filtering.)
If you're looking for an example of a subclassed field, this snippet might be of use. Its goal is similar (multiple choices as a model field), but its manner of storing them in the database differs (it's using a CSV text field instead of bit flags).
Working off the snippet in Andrew's answer, here are the changes you'd need to make:
from django.db import models
from django import forms
class BitFlagFormField(forms.MultipleChoiceField):
widget = forms.CheckboxSelectMultiple
def __init__(self, *args, **kwargs):
super(BitFlagFormField, self).__init__(*args, **kwargs)
class BitFlagField(models.Field):
__metaclass__ = models.SubfieldBase
def get_internal_type(self):
return "Integer"
def get_choices_default(self):
return self.get_choices(include_blank=False)
def _get_FIELD_display(self, field):
value = getattr(self, field.attname)
choicedict = dict(field.choices)
def formfield(self, **kwargs):
# do not call super, as that overrides default widget if it has choices
defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name),
'help_text': self.help_text, 'choices':self.choices}
if self.has_default():
defaults['initial'] = self.get_default()
defaults.update(kwargs)
return BitFlagFormField(**defaults)
def get_db_prep_value(self, value):
if isinstance(value, int):
return value
elif isinstance(value, list):
return sum(value)
def to_python(self, value):
result = []
n = 1
while value > 0:
if (value % 2) > 0:
result.append(n)
n *= 2
value /= 2
return sorted(result)
def contribute_to_class(self, cls, name):
super(BitFlagField, self).contribute_to_class(cls, name)
if self.choices:
func = lambda self, fieldname = name, choicedict = dict(self.choices):" and ".join([choicedict.get(value,value) for value in getattr(self,fieldname)])
setattr(cls, 'get_%s_display' % self.name, func)
This is how I would use the flags with my User class:
FLAGS = {
1:"Blacklisted",
2:"SpecialGuest",
4:"AttendAd-hocSessions",
8:"AttendVIPSessions",
16:"AccessFacilityA",
32:"AccessFacilityB",
}
class User(object):
def __init__(self, name="John Doe", groups=0):
self.name = name
self.groups = groups
def memberof(self):
''' Display string representation of the groups. '''
for flag in sorted(FLAGS):
if (flag & self.groups) == flag:
print FLAGS[flag]
Of course instead of printing the flags, you can create a comma-separated string to display in the admin view, or whatever you desire.
For the admin, just use a boolean for each of the group values.

Set Django IntegerField by choices=... name

When you have a model field with a choices option you tend to have some magic values associated with human readable names. Is there in Django a convenient way to set these fields by the human readable name instead of the value?
Consider this model:
class Thing(models.Model):
PRIORITIES = (
(0, 'Low'),
(1, 'Normal'),
(2, 'High'),
)
priority = models.IntegerField(default=0, choices=PRIORITIES)
At some point we have a Thing instance and we want to set its priority. Obviously you could do,
thing.priority = 1
But that forces you to memorize the Value-Name mapping of PRIORITIES. This doesn't work:
thing.priority = 'Normal' # Throws ValueError on .save()
Currently I have this silly workaround:
thing.priority = dict((key,value) for (value,key) in Thing.PRIORITIES)['Normal']
but that's clunky. Given how common this scenario could be I was wondering if anyone had a better solution. Is there some field method for setting fields by choice name which I totally overlooked?
Do as seen here. Then you can use a word that represents the proper integer.
Like so:
LOW = 0
NORMAL = 1
HIGH = 2
STATUS_CHOICES = (
(LOW, 'Low'),
(NORMAL, 'Normal'),
(HIGH, 'High'),
)
Then they are still integers in the DB.
Usage would be thing.priority = Thing.NORMAL
As of Django 3.0, you can use:
class ThingPriority(models.IntegerChoices):
LOW = 0, 'Low'
NORMAL = 1, 'Normal'
HIGH = 2, 'High'
class Thing(models.Model):
priority = models.IntegerField(default=ThingPriority.LOW, choices=ThingPriority.choices)
# then in your code
thing = get_my_thing()
thing.priority = ThingPriority.HIGH
I'd probably set up the reverse-lookup dict once and for all, but if I hadn't I'd just use:
thing.priority = next(value for value, name in Thing.PRIORITIES
if name=='Normal')
which seems simpler than building the dict on the fly just to toss it away again;-).
Here's a field type I wrote a few minutes ago that I think does what you want. Its constructor requires an argument 'choices', which may be either a tuple of 2-tuples in the same format as the choices option to IntegerField, or instead a simple list of names (ie ChoiceField(('Low', 'Normal', 'High'), default='Low') ). The class takes care of the mapping from string to int for you, you never see the int.
class ChoiceField(models.IntegerField):
def __init__(self, choices, **kwargs):
if not hasattr(choices[0],'__iter__'):
choices = zip(range(len(choices)), choices)
self.val2choice = dict(choices)
self.choice2val = dict((v,k) for k,v in choices)
kwargs['choices'] = choices
super(models.IntegerField, self).__init__(**kwargs)
def to_python(self, value):
return self.val2choice[value]
def get_db_prep_value(self, choice):
return self.choice2val[choice]
Model's choices option accepts a sequence consisting itself of iterables of exactly two items (e.g. [(A, B), (A, B) ...]) to use as choices for this field.
In addition, Django provides enumeration types that you can subclass to define choices in a concise way:
from django.utils.translation import gettext_lazy as _
class ThingPriority(models.IntegerChoices):
LOW = 0, _('Low')
NORMAL = 1, _('Normal')
HIGH = 2, _('High')
class Thing(models.Model):
priority = models.IntegerField(default=ThingPriority.NORMAL, choices=ThingPriority.choices)
Django supports adding an extra string value to the end of this tuple to be used as the human-readable name, or label. The label can be a lazy translatable string.
# in your code
thing = get_thing() # instance of Thing
thing.priority = ThingPriority.LOW
Note: you can use that using ThingPriority.HIGH, ThingPriority.['HIGH'], or ThingPriority(0) to access or lookup enum members.
You need to import from django.utils.translation import gettext_lazy as _
I appreciate the constant defining way but I believe Enum type is far best for this task. They can represent integer and a string for an item in the same time, while keeping your code more readable.
Enums were introduced to Python in version 3.4. If you are using any lower (such as v2.x) you can still have it by installing the backported package: pip install enum34.
# myapp/fields.py
from enum import Enum
class ChoiceEnum(Enum):
#classmethod
def choices(cls):
choices = list()
# Loop thru defined enums
for item in cls:
choices.append((item.value, item.name))
# return as tuple
return tuple(choices)
def __str__(self):
return self.name
def __int__(self):
return self.value
class Language(ChoiceEnum):
Python = 1
Ruby = 2
Java = 3
PHP = 4
Cpp = 5
# Uh oh
Language.Cpp._name_ = 'C++'
This is pretty much all. You can inherit the ChoiceEnum to create your own definitions and use them in a model definition like:
from django.db import models
from myapp.fields import Language
class MyModel(models.Model):
language = models.IntegerField(choices=Language.choices(), default=int(Language.Python))
# ...
Querying is icing on the cake as you may guess:
MyModel.objects.filter(language=int(Language.Ruby))
# or if you don't prefer `__int__` method..
MyModel.objects.filter(language=Language.Ruby.value)
Representing them in string is also made easy:
# Get the enum item
lang = Language(some_instance.language)
print(str(lang))
# or if you don't prefer `__str__` method..
print(lang.name)
# Same as get_FOO_display
lang.name == some_instance.get_language_display()
class Sequence(object):
def __init__(self, func, *opts):
keys = func(len(opts))
self.attrs = dict(zip([t[0] for t in opts], keys))
self.choices = zip(keys, [t[1] for t in opts])
self.labels = dict(self.choices)
def __getattr__(self, a):
return self.attrs[a]
def __getitem__(self, k):
return self.labels[k]
def __len__(self):
return len(self.choices)
def __iter__(self):
return iter(self.choices)
def __deepcopy__(self, memo):
return self
class Enum(Sequence):
def __init__(self, *opts):
return super(Enum, self).__init__(range, *opts)
class Flags(Sequence):
def __init__(self, *opts):
return super(Flags, self).__init__(lambda l: [1<<i for i in xrange(l)], *opts)
Use it like this:
Priorities = Enum(
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High')
)
priority = models.IntegerField(default=Priorities.LOW, choices=Priorities)
Simply replace your numbers with the human readable values you would like. As such:
PRIORITIES = (
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High'),
)
This makes it human readable, however, you'd have to define your own ordering.
My answer is very late and might seem obvious to nowadays-Django experts, but to whoever lands here, i recently discovered a very elegant solution brought by django-model-utils: https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices
This package allows you to define Choices with three-tuples where:
The first item is the database value
The second item is a code-readable value
The third item is a human-readable value
So here's what you can do:
from model_utils import Choices
class Thing(models.Model):
PRIORITIES = Choices(
(0, 'low', 'Low'),
(1, 'normal', 'Normal'),
(2, 'high', 'High'),
)
priority = models.IntegerField(default=PRIORITIES.normal, choices=PRIORITIES)
thing.priority = getattr(Thing.PRIORITIES.Normal)
This way:
You can use your human-readable value to actually choose the value of your field (in my case, it's useful because i'm scraping wild content and storing it in a normalized way)
A clean value is stored in your database
You have nothing non-DRY to do ;)
Enjoy :)
Originally I used a modified version of #Allan's answer:
from enum import IntEnum, EnumMeta
class IntegerChoiceField(models.IntegerField):
def __init__(self, choices, **kwargs):
if hasattr(choices, '__iter__') and isinstance(choices, EnumMeta):
choices = list(zip(range(1, len(choices) + 1), [member.name for member in list(choices)]))
kwargs['choices'] = choices
super(models.IntegerField, self).__init__(**kwargs)
def to_python(self, value):
return self.choices(value)
def get_db_prep_value(self, choice):
return self.choices[choice]
models.IntegerChoiceField = IntegerChoiceField
GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')
class Gear(Item, models.Model):
# Safe to assume last element is largest value member of an enum?
#type = models.IntegerChoiceField(GEAR, default=list(GEAR)[-1].name)
largest_member = GEAR(max([member.value for member in list(GEAR)]))
type = models.IntegerChoiceField(GEAR, default=largest_member)
def __init__(self, *args, **kwargs):
super(Gear, self).__init__(*args, **kwargs)
for member in GEAR:
setattr(self, member.name, member.value)
print(Gear().HEAD, (Gear().HEAD == GEAR.HEAD.value))
Simplified with the django-enumfields package package which I now use:
from enumfields import EnumIntegerField, IntEnum
GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')
class Gear(Item, models.Model):
# Safe to assume last element is largest value member of an enum?
type = EnumIntegerField(GEAR, default=list(GEAR)[-1])
#largest_member = GEAR(max([member.value for member in list(GEAR)]))
#type = EnumIntegerField(GEAR, default=largest_member)
def __init__(self, *args, **kwargs):
super(Gear, self).__init__(*args, **kwargs)
for member in GEAR:
setattr(self, member.name, member.value)

Categories