Source code for superdjango.shortcuts.library

"""
Library of shortcuts.

"""

# Imports

from django.apps import apps as django_apps
from django.conf import settings
from django.contrib.staticfiles import finders
from django.core.exceptions import ImproperlyConfigured
from django.db.models import AutoField
from django.template import Context, loader, Template
from django.template.exceptions import TemplateDoesNotExist
from django.utils.module_loading import module_has_submodule
from importlib import import_module
from itertools import chain
from operator import attrgetter
from superdjango.conf import SUPERDJANGO
import warnings

# Exports

__all__ = (
    "copy_model_instance",
    "get_app_modules",
    "get_model",
    "get_setting",
    "get_setting_with_prefix",
    "get_user_name",
    "has_o2o",
    "parse_string",
    "parse_template",
    "static_file_exists",
    "template_exists",
    "there_can_be_only_one",
    "title",
    "user_in_group",
    # "CombinedQuerySet",
    "CompareQuerySet",
)

# Constants

DEBUG = getattr(settings, "DEBUG", False)

# Functions


[docs]def copy_model_instance(instance): """Copy an instance of a model. :param instance: The model instance to be copied. :type instance: Model :returns: Returns a copy of the model instance. .. tip:: The new instance has *not* been saved to the database. Also, reference fields must be manually recreated using the original instance. """ initial_values = dict() # noinspection PyProtectedMember for f in instance._meta.fields: # noinspection PyProtectedMember if not isinstance(f, AutoField) and f not in list(instance._meta.parents.values()): initial_values[f.name] = getattr(instance, f.name) return instance.__class__(**initial_values)
[docs]def get_app_modules(name): """Yields tuples of ``(app_name, module)`` for each installed app that includes the given module name. :param name: The module name. :type name: str """ for app in django_apps.get_app_configs(): if module_has_submodule(app.module, name): yield app.name, import_module("%s.%s" % (app.name, name))
[docs]def get_model(dotted_path, raise_exception=True): """Get the model for the given dotted path. :param dotted_path: The ``app_label.ModelName`` path of the model to return. :type dotted_path: str :param raise_exception: Raise an exception on failure. :type raise_exception: bool """ try: return django_apps.get_model(dotted_path, require_ready=False) except ValueError: if raise_exception: raise ImproperlyConfigured('dotted_path must be in the form of "app_label.ModelName".') except LookupError: if raise_exception: raise ImproperlyConfigured("dotted_path refers to a model that has not been installed: %s" % dotted_path) return None
[docs]def get_setting(name, default=None): """Get the value of the named setting from the ``settings.py`` file. :param name: The name of the setting. :type name: str :param default: The default value. """ message = "get_settings() will likely be removed in the next minor version. Use " \ "conf.SUPERDJANGO.get() instead." warnings.warn(message, PendingDeprecationWarning) return getattr(settings, name.upper(), default)
[docs]def get_setting_with_prefix(name, default=None, prefix="SUPERDJANGO"): """Get the value of the named setting from the ``settings.py`` file. :param name: The name of the setting. :type name: str :param default: The default value. :param prefix: The setting prefix. :type prefix: str """ message = "get_setting_with_prefix() will likely be removed in the next minor version. Use " \ "conf.SUPERDJANGO.get() instead." warnings.warn(message, PendingDeprecationWarning) full_name = "%s_%s" % (prefix, name.upper()) return get_setting(full_name, default=default)
[docs]def get_user_name(user): """Get the full name of the user, if available, or just the value of the username field if not. :param user: The user instance. :rtype: str | None :raises: ``AttributeError`` if a custom user model has been implemented without a ``get_full_name()`` method. .. tip:: It is safe to call this function at runtime where user is ``None``. """ if user is None: return None full_name = user.get_full_name() if len(full_name) > 0: return full_name return getattr(user, user.USERNAME_FIELD)
[docs]def has_o2o(instance, field_name): """Test whether a OneToOneField is populated. :param instance: The model instance to check. :type instance: object :param field_name: The name of the one to one field. :type field_name: str || unicode :rtype: bool This is the same `hasattr()``, but is more explicit and easier to remember. See http://devdocs.io/django/topics/db/examples/one_to_one """ return hasattr(instance, field_name)
[docs]def parse_string(string, context): """Parse a string as a Django template. :param string: The name of the template. :type string: str :param context: Context variables. :type context: dict :rtype: str """ message = "parse_string() has been moved to superdjango.html.shortcuts and will be removed in the next " \ "minor version." warnings.warn(message, PendingDeprecationWarning) template = Template(string) return template.render(Context(context))
[docs]def parse_template(template, context): """Ad hoc means of parsing a template using Django's built-in loader. :param template: The name of the template. :type template: str || unicode :param context: Context variables. :type context: dict :rtype: str """ message = "parse_template() has been moved to superdjango.html.shortcuts and will be removed in the next " \ "minor version." warnings.warn(message, PendingDeprecationWarning) return loader.render_to_string(template, context)
[docs]def static_file_exists(path, tokens=None): """Determine whether a static file exists. :param path: The path to be checked. :type path: str :param tokens: If given, format will be used to replace the tokens in the path. :type tokens: dict :rtype: bool """ if tokens is not None: path = path.format(**tokens) return finders.find(path) is not None
[docs]def template_exists(name): """Indicates whether the given template exists. :param name: The name (path) of the template to load. :type name: str :rtype: bool """ message = "template_exists() has been moved to superdjango.html.shortcuts and will be removed in the next " \ "minor version." warnings.warn(message, PendingDeprecationWarning) try: loader.get_template(name) return True except TemplateDoesNotExist: return False
[docs]def there_can_be_only_one(cls, instance, field_name): """Helper function that ensures a given boolean field is ``True`` for only one record in the database. It is intended for use with ``pre_save`` signals. :param cls: The class (sender) emitting the signal. :type cls: class :param instance: The instance to be checked. :type: instance: object :param field_name: The name of the field to be checked. Must be a ``BooleanField``. :type field_name: str | unicode :raises: ``AttributeError`` if the ``field_name`` does not exist on the ``instance``. """ field_value = getattr(instance, field_name) if field_value: cls.objects.update(**{field_name: False})
[docs]def title(value, uppers=None): """Smart title conversion. :param value: The value to converted to Title Case. :type value: str :param uppers: A list of keywords that are alway upper case. :type uppers: list[str] :rtype: str """ if uppers is None: uppers = list() uppers += SUPERDJANGO.UPPERS tokens = value.split(" ") _value = list() for t in tokens: if t.lower() in uppers: v = t.upper() else: v = t.title() _value.append(v) return " ".join(_value)
[docs]def user_in_group(user, group): """Determine whether a given user is in a particular group. :param user: The user instance to be checked. :type user: User :param group: The name of the group. :type group: str :rtype: bool """ return user.groups.filter(name=group).exists()
# Classes # TODO: Original ideas for CombinedQuerySet doesn't really work. Needs lots of thought and testing to mimic a queryset. # Will work on this again when I have a need for a combined qs. # class CombinedQuerySet(object): # """Combines two are more querysets as though they were one.""" # # def __init__(self, querysets=None): # if querysets is not None: # self.rows = querysets # else: # self.rows = list() # # if querysets is not None: # self.results = list(chain(*querysets)) # else: # self.results = list() # # def append(self, qs): # self.results.append(chain(qs)) # # def filter(self, **criteria): # _results = list() # for qs in self.results: # new_qs = qs.filter(**criteria) # _results.append(new_qs) # # return CombinedQuerySet(querysets=_results) # # def order_by(self, field_name): # return sorted(chain(*self.results), key=attrgetter(field_name))
[docs]class CompareQuerySet(object): """Compare two querysets to see what's been added or removed."""
[docs] def __init__(self, qs1, qs2): """Initialize the comparison. :param qs1: The primary or "authoritative" queryset. :type qs1: django.db.models.QuerySet :param qs2: The secondary queryset. :type qs2: django.db.models.QuerySet """ self.qs1 = qs1 self.qs2 = qs2 self._pk1 = list() for row in qs1: self._pk1.append(row.pk) self._pk2 = list() for row in qs2: self._pk2.append(row.pk)
[docs] def get_added(self): """Get the primary keys of records in qs2 that do not appear in qs1. :rtype: list """ # Faster method for larger querysets? # https://stackoverflow.com/a/3462160/241720 a = list() for pk in self._pk2: if pk not in self._pk1: a.append(pk) return a
[docs] def get_removed(self): """Get the primary keys of records in qs2 that do not appear in qs1. :rtype: list """ # Faster method for larger querysets? # https://stackoverflow.com/a/3462160/241720 a = list() for pk in self._pk2: if pk not in self._pk1: a.append(pk) return a
[docs] def is_different(self): """Indicates whether the two querysets are different. :rtype: bool """ return set(self._pk1) != set(self._pk2)
[docs] def is_same(self): """Indicates whether the two querysets are the same. :rtype: bool """ return set(self._pk1) == set(self._pk2)