Skip to content

Instantly share code, notes, and snippets.

@pymen
Last active July 9, 2020 18:31
Show Gist options
  • Save pymen/454688312c3401c8a035d13b1acae6a9 to your computer and use it in GitHub Desktop.
Save pymen/454688312c3401c8a035d13b1acae6a9 to your computer and use it in GitHub Desktop.
SmartChoices
from collections import ChainMap
from copy import deepcopy
from model_utils import Choices
import string
### HELPERS STARTED
def merge_dicts(*dicts):
"""Merge multiple dicts into one
in case of intersections values from last dict will overwrite prev ones"""
return {**ChainMap(*reversed(dicts))}
def to_list(anytype):
if type(anytype) is tuple:
return list(anytype)
elif type(anytype) is set:
return list(anytype)
elif type(anytype) is dict:
return list(anytype.keys())
elif type(anytype) is not list:
return [anytype]
return anytype
def set_attrs(obj, attrs, **kwargs):
for key, value in merge_dicts(attrs, kwargs).items():
setattr(obj, key, value)
def get_attrs(obj, attrs):
return {attr: getattr(obj, attr) for attr in attrs}
### HELPERS FINISHED
class BetterChoices(Choices):
"""Same as Choices with several helper methods
A Choices object is initialized with any number of choices.
In the simplest case, each choice is a string
STATUS = BetterChoices('draft', 'published')
that string will be used both as the database representation of the choice,
and the human-readable representation.
Note that you can access options as attributes on the Choices object: STATUS.draft.
choices as two-tuples:
BetterChoices(('draft', _('draft')), ('published', _('published')))
choices as triples, where the
- first element is the database representation,
- the second is a valid Python identifier you will use in your code as a constant,
- the third is the human-readable version
STATUS = BetterChoices((0, 'draft', _('draft')), (1, 'published', _('published')))
"""
def get_reverse_map(self):
return {v: k for k, v in self._identifier_map.items()}
def get_db_name_by_id(self, id):
return self.get_reverse_map()[id]
def exclude(self, *choice_ids):
without = [t for t in self._triples if t[0] not in choice_ids]
return self.__class__(*without)
def only(self, *choice_ids):
without = [t for t in self._triples if t[0] in choice_ids]
return self.__class__(*without)
class SmartChoices:
"""Better then BetterChoices, because uses direct attributes which enables autocomplete in IDE"""
_HUMAN_REPR = {}
@classmethod
def vars(cls):
return [k for k in cls.__dict__.keys() if k[0] in string.ascii_uppercase]
@classmethod
def dict(cls):
return get_attrs(cls, cls.vars())
@classmethod
def triples(cls):
result = []
for key, choice_id in cls.dict().items():
human_value = cls._HUMAN_REPR.get(choice_id, key)
result.append((choice_id, key, human_value))
return result
@classmethod
def choices(cls, exclude=None, only=None):
choices = BetterChoices(*cls.triples())
if exclude:
return choices.exclude(*to_list(exclude))._doubles
elif only:
return choices.only(*to_list(only))._doubles
else:
return choices._doubles
# DEFINITION
class ORDER_CREATOR(SmartChoices):
USER = 1
SYSTEM = 2
class TEST_ARTICLE_TYPE(SmartChoices):
ACCOUNT = 101
TEMPLATE = 102
MAILS = 103
_HUMAN_REPR = {
ACCOUNT: 'Human1',
TEMPLATE: 'Human2',
}
# USAGE 1
class Model:
created_by = models.PositiveIntegerField(choices=ORDER_CREATOR.choices(), default=ORDER_CREATOR.USER)
# USAGE 2
qs = Order.objects.filter(
created_by=ORDER_CREATOR.SYSTEM,
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment