PKuRO;belt/__init__.py"""Django app made to get together a set of utilities to use with Django projects.""" __version__ = "1.3.0" default_app_config = "belt.apps.BeltConfig" PKOPGOgz\ belt/apps.pyfrom django.apps import AppConfig from django.utils.translation import ugettext_lazy as _ class BeltConfig(AppConfig): """Django Belt AppConfig.""" name = "belt" verbose_name = _("Belt") PKOPGOaa:!!belt/commands.pyimport os class ProgressBarCommand(object): """Mixin to add a progress bar to a management command.""" default_terminal_size = 25 progress_symbol = "=" def terminal_size(self): """Gets the terminal columns size.""" try: _, columns = os.popen("stty size", "r").read().split() return min(int(columns) - 10, 100) except ValueError: return self.default_terminal_size def bar(self, progress): """Shows on the stdout the progress bar for the given progress.""" if not hasattr(self, "_limit") or not self._limit: self._limit = self.terminal_size() graph_progress = int(progress * self._limit) self.stdout.write("\r", ending="") progress_format = "[%-{}s] %d%%".format(self._limit) self.stdout.write( self.style.SUCCESS( progress_format % (self.progress_symbol * graph_progress, int(progress * 100)) ), ending="", ) self.stdout.flush() PKOPGOj88belt/decorators.pyimport os import shutil def delete_after(filename): """Decorator to be sure the file given by parameter is deleted after the execution of the method. """ def delete_after_decorator(function): def wrapper(*args, **kwargs): try: return function(*args, **kwargs) finally: if os.path.isfile(filename): os.remove(filename) if os.path.isdir(filename): shutil.rmtree(filename) return wrapper return delete_after_decorator PKPGO" belt/files.pyimport hashlib import os import random import time from django.utils.deconstruct import deconstructible from django.utils.text import slugify @deconstructible class UploadToDir(object): """Generates a function to give to ``upload_to`` parameter in models.Fields, that generates an name for uploaded files based on ``populate_from`` attribute. """ def __init__(self, path, populate_from=None, prefix=None, random_name=False): self.path = path self.populate_from = populate_from self.random_name = random_name self.prefix = prefix def __call__(self, instance, filename): """Generates an name for an uploaded file.""" if self.populate_from is not None and not hasattr(instance, self.populate_from): raise AttributeError( "Instance hasn't {} attribute".format(self.populate_from) ) ext = filename.split(".")[-1] readable_name = slugify(filename.split(".")[0]) if self.populate_from: readable_name = slugify(getattr(instance, self.populate_from)) if self.random_name: random_name = hashlib.sha256( "{}--{}".format(time.time(), random.random()).encode("utf-8") ) readable_name = random_name.hexdigest() elif self.prefix is not None: readable_name = f"{self.prefix}{readable_name}" file_name = "{}.{}".format(readable_name, ext) return os.path.join(self.path, file_name) PKOPGObelt/generators.pyimport random from django.utils.crypto import get_random_string def unique_model_random_string( model, field, length=16, allowed_chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", ): """ Generates a random alphanumeric string, unique in code field from a django class model :param length: Length of the generated string. :param model: Model class, it has a code field. :param field: field name to be sure is unique. :param allowed_chars: allowed chars to select. """ value = get_random_string(length, allowed_chars=allowed_chars) while model.objects.filter(**{field: value}).exists(): length += random.choice([-1, 1]) value = get_random_string(length, allowed_chars=allowed_chars) return value PKOPGOSbelt/managers.pyfrom functools import reduce from django.db.models import Q class SearchQuerySetMixin: """Mixin to add a searches method to a custom QuerySet.""" def search(self, query): """Search action in housings.""" try: fields = self.model.SEARCH_FIELDS except AttributeError: fields = [] conditions = [Q(**{f"{field}__icontains": query}) for field in fields] if conditions: return self.filter(reduce(lambda x, y: x | y, conditions)) return self.none() PKuROҌbelt/models.pyimport warnings from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import ugettext_lazy as _ def transition_handler_decorator(func): """Decorator for transitions methods. Allows to activate a flag if the transition is running, and also saves the original status value. """ if not func: return func def _transition_wrapper(self=None, *args, **kwargs): self._original_status = self.get_status_transition()[0] self._handling_transition = True result = func(*args, **kwargs) self._handling_transition = False return result return _transition_wrapper class StatusMixin(models.Model): """Mixin to handle status changes""" STATUS_FIELD = "status" ALLOWED_TRANSITIONS = [] FORBIDDEN_TRANSITIONS = [] TRANSITION_HANDLERS = {} class Meta: abstract = True def __init__(self, *args, **kwargs): """Init _handling_transition value to False.""" self._handling_transition = False super().__init__(*args, **kwargs) self._original_status = getattr(self, self.STATUS_FIELD) def refresh_from_db(self, using=None, fields=None): super().refresh_from_db(using=using, fields=fields) if hasattr(self, "_transition"): delattr(self, "_transition") def get_status_transition(self): """Get status transition.""" if self.pk: if hasattr(self, "_transition"): return self._transition previous = self._meta.model.objects.get(pk=self.pk) if previous.status != getattr(self, self.STATUS_FIELD): self._transition = previous.status, getattr(self, self.STATUS_FIELD) return self._transition def validate_transition(self): """Validates the transition.""" transition = self.get_status_transition() # Check the transition is not a allowed transitions if ( transition and self.ALLOWED_TRANSITIONS and transition not in self.ALLOWED_TRANSITIONS ): raise ValidationError( _( f"A {self._meta.model_name} can't change from " f"{transition[0]} to {transition[1]}" ) ) # Check the transition is not a forbidden transitions if ( transition and self.FORBIDDEN_TRANSITIONS and transition in self.FORBIDDEN_TRANSITIONS ): raise ValidationError( _( f"A {self._meta.model_name} can't change from " f"{transition[0]} to {transition[1]}" ) ) def pre_status_handler(self, transition): """Method used to execute code before the status handler is called.""" pass def post_status_handler(self, transition): """Method used to execute code after the status handler is called.""" pass def get_transition_handler(self): """Get the transition handler between status.""" transition = self.get_status_transition() if transition: handler_name = self.TRANSITION_HANDLERS.get(transition, "") transition_handler = ( getattr(self, handler_name) if hasattr(self, handler_name) else None ) return transition_handler_decorator(transition_handler) def clean(self): """Validates transition in clean.""" self.validate_transition() def save(self, *args, **kwargs): # Checks if the is a transition during a handling if self._handling_transition and self._original_status != getattr( self, self.STATUS_FIELD ): setattr(self, self.STATUS_FIELD, self._original_status) warnings.warn( Warning( "Status changes during the execution of transitions handlers are not allowed" ) ) # Gets the handler before the save transition = self.get_status_transition() transition_handler = self.get_transition_handler() super().save(*args, **kwargs) # Executes the handler after the save if transition_handler and not self._handling_transition: self.pre_status_handler(transition) transition_handler(self) self.post_status_handler(transition) PKOPGObelt/django_filters/__init__.pyPKOPGOūbelt/django_filters/filters.pyfrom django_filters import CharFilter class SearchFilter(CharFilter): """Filter to use searches QuerySet method.""" def filter(self, qs, value): if hasattr(qs, "search"): return qs.search(query=value) return qs PKOPGObelt/rest_framework/__init__.pyPKuROm"11belt/rest_framework/mixins.pyfrom rest_framework.serializers import Serializer from django.db.models import QuerySet from rest_framework.response import Response class ActionSerializersMixin: """ Mixin for ViewSets that allows to have a dict with a serializer for each action. """ action_serializers = {} def get_serializer_class(self): if hasattr(self, "action_serializers"): if self.action in self.action_serializers: return self.action_serializers[self.action] return super().get_serializer_class() class SimpleSerializationMixin: """ Mixin to simplify serialization of object instances. """ def serialize_instance(self, data) -> Serializer: serializer_class = self.get_serializer_class() serializer = serializer_class(instance=data) return serializer.data class SimplePaginationMixin: """ Mixin to simplify pagination. """ def paginate(self, queryset: QuerySet) -> Response: page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) class SimpleValidationMixin: """ Mixin to simplify validation. """ def validate(self, data: dict, raise_exception=True) -> Serializer: serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=raise_exception) return serializer PKFGO88#django_belt-1.3.0.dist-info/LICENSEThe MIT License (MIT) Copyright (c) 2017 Marcos Gabarda Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.PK!HPO!django_belt-1.3.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UrPK!H f$django_belt-1.3.0.dist-info/METADATATMs0WpIK+Ifzp4-SBh!1#ˋQ-UI P>&`}zz /oN* E d5O8:ޱq]6 0 Rd^Cw8)^+tR;;gpc-J"]ȍ){8 XeL9vHD]@IE]  /[ \A;'2hC+o-Iw08tGqx=q:(Ԝərk=: TGo_SPK!H T"django_belt-1.3.0.dist-info/RECORDɒX}> d1]E-PPFeeC tA&IH3UkR2!{R(fI`O- z0%Cp@]?C+pfwcS:sk:l6JJB#mƯ"=u߹v6e?<3IRJt`8>ZmӅ)'a>BK]iUwo[#1@_RZ+"2jxUvb\Edgo0y\N?yL5(`Tbf;9q6tSP! IYE?$iM\ae BP*LԩsUcwP0!V սnN~XV,o<?!౥oeA46vx(Pd&VՀ)'s{ڹWf(^ӣPP'{_ EVo[!Sa >l{WSM5i\TQgYH 3.Sf +Z(#_lQԅo?q'߱FiKmGWY2o tO&>AROV2*+QO4XGۓDQsňb kR8N ŪI4~Q^oke⣙?d:Aq:[h]z'G%b)PKuRO;belt/__init__.pyPKOPGOgz\ belt/apps.pyPKOPGOaa:!!belt/commands.pyPKOPGOj88 belt/decorators.pyPKPGO" sbelt/files.pyPKOPGObelt/generators.pyPKOPGOSbelt/managers.pyPKuROҌbelt/models.pyPKOPGO%belt/django_filters/__init__.pyPKOPGOū &belt/django_filters/filters.pyPKOPGOB'belt/rest_framework/__init__.pyPKuROm"11'belt/rest_framework/mixins.pyPKFGO88#-django_belt-1.3.0.dist-info/LICENSEPK!HPO!d2django_belt-1.3.0.dist-info/WHEELPK!H f$2django_belt-1.3.0.dist-info/METADATAPK!H T"5django_belt-1.3.0.dist-info/RECORDPK^8