Last active
July 9, 2020 18:31
-
-
Save pymen/454688312c3401c8a035d13b1acae6a9 to your computer and use it in GitHub Desktop.
SmartChoices
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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