Source code for disbi.utils

"""
Some utility functions used throughout the DISBi app.
"""
# standard library
from collections import OrderedDict
from copy import deepcopy

# Django
from django.conf import settings
from django.db import models


[docs]def clean_set(cleaned_formset_data): """Return a dictionary with keys from POST and lists as values. The cleaned_data of a formset is a list of dictionaries that can have overlapping keys. The function changes that into a dictionary where each key corresponds to list of values that belonged to the same key. Returns: dict: Dictionary with items joined on keys. """ cleaned_set = {} for clean_data in cleaned_formset_data: for attr, val in clean_data.items(): # Turn empty string placeholder back into empty string, # thus the empyt string can be passed through the froms clean() method. if val == settings.DISBI['EMPTY_STR']: val = '' if attr in cleaned_set.keys(): cleaned_set[attr].append(val) else: cleaned_set[attr] = [val] return cleaned_set
[docs]def construct_none_displayer(entries, placeholder='-'): """Return a string to be used as a placeholder for an empty option. The length of the placeholder is based on the length of the longest option in a list of entries. """ # Convert to string as a precaution to figure out length. entries = [str(e) for e in entries] # Get length of longest string from list max_len = len(max(entries, key=len)) # Insert display option for choosing None if max_len > 4: none_dislplayer = placeholder * (max_len+1) else: none_dislplayer = placeholder * 5 return none_dislplayer
[docs]def get_choices(choice_tup, style='db'): """ Return the choices given on a model Field. Args: choice_tup: The choice tuple given in the model. Keyword Args: style: Determines whether the human readable "display" values should be returned or those from the "db". """ if style == 'db': idx = 0 elif style == 'display': idx = 1 else: raise ValueError # check whether the tuple contains extra optgroup if isinstance(choice_tup[0][1], tuple): optgroup = True else: optgroup = False output_choices = [] if optgroup: for group in choice_tup: for pair in group[1]: output_choices.append(pair[idx]) else: for pair in choice_tup: output_choices.append(pair[idx]) return output_choices
[docs]def get_optgroups(choice_tup, style='db'): """ Parse the choices given on a model Field and map them to their groups. Args: choice_tup: The choice tuple given in the model. Keyword Args: style: Determines whether the human readable "display" values should be returned or those from the "db". Returns: dict: A list of choices mapped to their optgroup. """ if style == 'db': idx = 0 elif style == 'display': idx = 1 else: raise ValueError('Style must either be "db" or "display".') # Check whether the tuple contains extra optgroup. if isinstance(choice_tup[0][1][0], str): optgroup = False else: optgroup = True output_choices = {} if optgroup: for group in choice_tup: opt_choices = [] for pair in group[1]: opt_choices.append(pair[idx]) output_choices[group[0]] = opt_choices else: raise ValueError('The choices must contain optgroups.') return output_choices
[docs]def get_hr_val(choices, db_val): """ Get the human readable value for the DB value from a choice tuple. Args: choices (tuple): The choice tuple given in the model. db_val: The respective DB value. Returns: The matching human readable value. """ for pair in choices: if pair[0] == db_val: return pair[1] # Value not found. return None
[docs]def remove_optgroups(choices): """ Remove optgroups from a choice tuple. Args: choices (tuple): The choice tuple given in the model. Returns: The n by 2 choice tuple without optgroups. """ # Check whether the tuple contains extra optgroup if isinstance(choices[0][1][0], str): optgroup = False else: optgroup = True output_choices = [] if optgroup: # Remove the optgroups and return the new tuple. for group in choices: for pair in group[1]: output_choices.append(pair) return tuple(output_choices) else: # The choices do not contain optgroups and can simply be returned. return choices
[docs]def reverse_dict(dic): """ Return a reversed dictionary. Each former value will be the key of a list of all keys that were mapping to it in the old dict. """ return {new_key: [old_key for old_key, old_val in dic.items() if old_val == new_key] for new_key in set(dic.values())}
[docs]def merge_dicts(a, b): """ Merge two dicts without modifying them inplace. Args: a (dict): The first dict. b (dict): The second dict. Overrides ``a`` on conflicts. Returns: dict: A merged dictionary. """ merged_dict = deepcopy(a) merged_dict.update(b) return merged_dict
[docs]def zip_dicts(a, b): """Merge two dicts, choose none empty value on key conflicts. The dicts are not modified inplace, but returned. Args: a (dict): The first dict. b (dict): The second dict. Overrides a on conflicts, when both values are none emtpy. Returns: dict: A merged dictionary. """ merged_dict = deepcopy(a) for k, v in b.items(): if k not in merged_dict.keys(): merged_dict[k] = v else: if v: merged_dict[k] = v
[docs]def object_view(obj, cols): """ Construct an external representation of an object based on a tuple of field/column names. Each column name is either expected to be a method or an attribute of the object. For the keys of the dict, the `short_description` is preferred. For attributes `verbose_name` is preferred over `name`. Args: obj: Any object with `cols` as attributes. cols (tuple): The column names. Returns: OrderedDict: The headers as keys and the entries as values. """ view = OrderedDict() for col in cols: # Will throw an error if the given col is no attribute. attr = obj.__getattribute__(col) if callable(attr): if hasattr(attr, 'short_description'): header = attr.short_description elif col == '__str__': header = obj.__class__.__name__ else: header = col view[header] = attr() else: try: field = obj.__class__._meta.get_field(col) header = field.verbose_name except: header = col if isinstance(attr, models.Model): attr = attr.__str__() view[header] = attr return view
[docs]def get_id_str(objects, delimiter='_'): """ Get a string of sorted ids, separated by a delimiter. Args: objects (Model): An iterable of model instances. Keyword Args: delimiter (str): The string the ids will be joined on. Returns: str: The joined and sorted id string. """ ids = [obj.id for obj in objects] ids.sort() ids = [str(identifier) for identifier in ids] id_str = delimiter.join(ids) return id_str
[docs]def get_ids(string, delimiter='_'): """ Get sorted `ids`. """ ids = string.split(delimiter) ids = [int(identifier) for identifier in ids] ids.sort() return ids
[docs]def get_unique(items): """ Get a list of unique items, even for non hashable items. """ unique_list = [] for item in items: if not item in unique_list: unique_list.append(item) return unique_list
[docs]def sort_by_other(sequence, order): """ Order a list a another list that contains the desired order. Args: sequence (list): The list that is ordered. All items must be contained in order. order (list): The list containing the order. Returns: list: The sequence ordered according to order. """ # Map the names of order to their indexes. order_dict = dict({(key, idx) for idx, key, in enumerate(order)}) # Get the indexes in respect to order from sequence. implicit_order = [order_dict[val] for val in sequence] # Zip and sort. return [val for (idx, val) in sorted(zip(implicit_order, sequence))]
[docs]def camelize(string, uppercase_first_letter=True): """ Convert a string with underscores to a camelCase string. Inspired by :func:`inflection.camelize` but even seems to run a little faster. Args: string (str): The string to be converted. uppercase_first_letter (bool): Determines whether the first letter of the string should be capitalized. Returns: str: The camelized string. """ if uppercase_first_letter: return ''.join([word.capitalize() for word in string.split('_')]) elif not uppercase_first_letter: words = [word.capitalize() for word in string.split('_')] words[0] = words[0].lower() return ''.join(words)