PK!pO*+options/__init__.py"""Simple app to add configuration options to a Django project.""" from options.constants import FLOAT, INT, STRING, FILE, TYPE_CHOICES, CONVERTER from options.helpers import get_option_model, get_user_option_model default_app_config = "options.apps.ConfigurationsConfig" __all__ = [ "get_option_model", "get_user_option_model", "FLOAT", "INT", "STRING", "FILE", "TYPE_CHOICES", "CONVERTER", ] __version__ = "2.0.1" PKnOD55options/admin.pyfrom django.contrib import admin from options import get_option_model, get_user_option_model Option = get_option_model() UserOption = get_user_option_model() @admin.register(Option) class OptionAdmin(admin.ModelAdmin): """Manage configuration options.""" list_display = ["public_name", "value"] search_fields = ["public_name", "name"] @admin.register(UserOption) class UserOptionAdmin(admin.ModelAdmin): """Manage configuration user options.""" list_display = ["user", "public_name", "value"] search_fields = ["public_name", "name"] PKnO%options/apps.pyimport logging from django.apps import AppConfig from django.db.models.signals import post_migrate from django.db.utils import IntegrityError from django.utils.translation import ugettext_lazy as _ from options import get_option_model from options.settings import DEFAULT_CONFIGURATION logger = logging.getLogger(__name__) def create_default_options(sender, **kwargs): """Creates the defaults configuration options if they don't exists.""" Option = get_option_model() for key, data in DEFAULT_CONFIGURATION.items(): if not Option.objects.filter(name=key).exists(): try: Option.objects.create(name=key, **data) except IntegrityError: logger.warning("Option '%s' already installed" % key) class ConfigurationsConfig(AppConfig): name = "options" verbose_name = _("Options") def ready(self): """Connects signals with their managers.""" post_migrate.connect(create_default_options) PKnOloptions/constants.pyfrom django.utils.translation import ugettext_lazy as _ FLOAT, INT, STRING, FILE = (0, 1, 2, 3) TYPE_CHOICES = ( (FLOAT, _("Float")), (INT, _("Integer")), (STRING, _("String")), (FILE, _("File")), ) CONVERTER = {INT: int, FLOAT: float, STRING: str, FILE: str} PKiPN)options/context_processors.pyfrom options.models import Option def options(request): """Context processor that adds options to the template context.""" return {option.name: option.get_value() for option in Option.objects.all()} PK!pOR[ options/helpers.pyimport hashlib import os import random import time from django.apps import apps as django_apps from django.core.exceptions import ImproperlyConfigured from django.utils.deconstruct import deconstructible from django.utils.text import slugify from options.constants import INT, FLOAT, STRING, CONVERTER def get_option_model(): """Return the Notification model that is active in this project.""" from options.settings import DEFAULT_OPTION_MODEL try: return django_apps.get_model(DEFAULT_OPTION_MODEL, require_ready=False) except ValueError: raise ImproperlyConfigured( "SIMPLE_OPTIONS_OPTION_MODEL must be of the form 'app_label.model_name'" ) except LookupError: raise ImproperlyConfigured( "SIMPLE_OPTIONS_OPTION_MODEL refers to model '%s' that has not " "been installed" % DEFAULT_OPTION_MODEL ) def get_user_option_model(): """Return the Notification model that is active in this project.""" from options.settings import DEFAULT_USER_OPTION_MODEL try: return django_apps.get_model(DEFAULT_USER_OPTION_MODEL, require_ready=False) except ValueError: raise ImproperlyConfigured( "SIMPLE_OPTIONS_USER_OPTION_MODEL must be of the form " "'app_label.model_name'" ) except LookupError: raise ImproperlyConfigured( "SIMPLE_OPTIONS_USER_OPTION_MODEL refers to model '%s' that has not " "been installed" % DEFAULT_OPTION_MODEL ) def convert_value(value, value_type): """Converts the given value to the given type.""" default_values = {INT: 0, FLOAT: 1.0, STRING: ""} try: option_value = CONVERTER.get(value_type, str)(value) except ValueError: option_value = default_values.get(value_type) return option_value @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) PKnOFoptions/managers.pyfrom django.db import models from options import get_option_model from options.settings import DEFAULT_EXCLUDE_USER class OptionManager(models.Manager): """Manager for options.""" def get_value(self, name, default=None): """Gets the value with the proper type.""" try: option = self.model.objects.get(name=name) return option.get_value() except self.model.DoesNotExist: return default class UserOptionManager(models.Manager): """Manager to handle user's custom options.""" def filter_user_customizable(self): """Returns option that the user can customize himself.""" return self.exclude(name__in=DEFAULT_EXCLUDE_USER) def get_value(self, name, user=None, default=None): """Gets the value with the proper type.""" Option = get_option_model() if user is None: return Option.objects.get_value(name=name, default=default) try: option = self.model.objects.get(user=user, name=name) return option.get_value() except self.model.DoesNotExist: return Option.objects.get_value(name=name, default=default) PKnOCF options/models.pyfrom django.conf import settings from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import ugettext_lazy as _ from options.constants import STRING, TYPE_CHOICES, CONVERTER, FILE from options.helpers import convert_value, UploadToDir from options.managers import OptionManager, UserOptionManager class BaseOption(models.Model): """Base model for system options and configurations.""" name = models.CharField( verbose_name=_("parameter name"), max_length=255, unique=True, db_index=True ) public_name = models.CharField( verbose_name=_("public name of the parameter"), max_length=255, unique=False, db_index=True, ) type = models.PositiveIntegerField(choices=TYPE_CHOICES, default=STRING) value = models.CharField( null=True, blank=True, default=None, max_length=256, verbose_name=_("value") ) file = models.FileField( upload_to=UploadToDir("options", random_name=True), null=True, blank=True ) is_list = models.BooleanField(default=False) class Meta: abstract = True def __str__(self): return f"{self.public_name}" def get_value(self): """Gets the value with the proper type. If the type is not valid it would return the default value for the field, to avoid problems with manual database modifications. """ # If the option is a file, returns the URL of the file if self.type == FILE and self.file is not None: return self.file.url if not self.is_list: return convert_value(self.value, self.type) else: values = self.value.split(",") return [convert_value(type, value) for value in values] def clean(self): """Calls to the converter to check the type conversion. Added exception for lists, to check all values. """ try: values = [self.value] if not self.is_list else self.value.split(",") [CONVERTER.get(self.type, str)(value) for value in values] except ValueError: raise ValidationError(_("Invalid value for this type.")) def save(self, *args, **kwargs): self.clean() super().save(*args, **kwargs) class Option(BaseOption): """System options and configurations.""" objects = OptionManager() class Meta: ordering = ["public_name"] swappable = "SIMPLE_OPTIONS_OPTION_MODEL" class UserOption(BaseOption): """Custom option for a user.""" user = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="options", on_delete=models.CASCADE ) name = models.CharField(verbose_name=_("Parameter"), max_length=255) objects = UserOptionManager() class Meta: unique_together = ["user", "name"] ordering = ["public_name"] swappable = "SIMPLE_OPTIONS_USER_OPTION_MODEL" PKnO O**options/settings.pyfrom django.conf import settings # Needed to build and publish with Flit # ------------------------------------------------------------------------------ SECRET_KEY = "snitch" # Specific project configuration # ------------------------------------------------------------------------------ # Set on settings the default options for this project. These will be created # on the post migrate signal handler. # Sample: # # SIMPLE_OPTIONS_CONFIGURATION = { # "sold_out": { # "value": 0, # "type": INT, # "public_name": "Sets tickets as sold out" # }, # } # DEFAULT_CONFIGURATION = getattr(settings, "SIMPLE_OPTIONS_CONFIGURATION", {}) # Set the list of options that the user can't customize. DEFAULT_EXCLUDE_USER = getattr(settings, "SIMPLE_OPTIONS_EXCLUDE_USER", tuple()) # Swappable Option model DEFAULT_OPTION_MODEL = getattr( settings, "SIMPLE_OPTIONS_OPTION_MODEL", "options.Option" ) # Swappable UserOption model DEFAULT_USER_OPTION_MODEL = getattr( settings, "SIMPLE_OPTIONS_USER_OPTION_MODEL", "options.UserOption" ) PKiPNoptions/management/__init__.pyPKiPN'options/management/commands/__init__.pyPKiPN-options/management/commands/export_options.pyimport json from django.core.management import BaseCommand from options.models import Option class Command(BaseCommand): help = "Export current options to JSON format." def handle(self, *args, **options): export = dict() for option in Option.objects.all(): export[option.name] = { "value": option.value, "type": option.type, "public_name": option.public_name, } self.stdout.write(json.dumps(export, indent=4, sort_keys=True)) PKiPN99"options/migrations/0001_initial.py# -*- coding: utf-8 -*- # Generated by Django 1.10.5 on 2017-02-23 15:53 from __future__ import unicode_literals from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [] operations = [ migrations.CreateModel( name="Option", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "name", models.CharField( db_index=True, max_length=255, unique=True, verbose_name="Parameter", ), ), ( "public_name", models.CharField( db_index=True, max_length=255, verbose_name="Public name of the parameter", ), ), ( "type", models.PositiveIntegerField( choices=[(0, "Float"), (1, "Integer"), (2, "String")], default=2 ), ), ( "value", models.CharField( blank=True, default=None, max_length=256, null=True, verbose_name="Value", ), ), ("is_list", models.BooleanField(default=False)), ], ) ] PKiPN -options/migrations/0002_auto_20181002_0254.py# Generated by Django 2.1 on 2018-10-02 07:54 from django.conf import settings from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("options", "0001_initial"), ] operations = [ migrations.CreateModel( name="UserOption", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "public_name", models.CharField( db_index=True, max_length=255, verbose_name="Public name of the parameter", ), ), ( "type", models.PositiveIntegerField( choices=[(0, "Float"), (1, "Integer"), (2, "String")], default=2 ), ), ( "value", models.CharField( blank=True, default=None, max_length=256, null=True, verbose_name="Value", ), ), ("is_list", models.BooleanField(default=False)), ("name", models.CharField(max_length=255, verbose_name="Parameter")), ( "user", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="options", to=settings.AUTH_USER_MODEL, ), ), ], options={"ordering": ["public_name"]}, ), migrations.AlterModelOptions( name="option", options={"ordering": ["public_name"]} ), migrations.AlterUniqueTogether( name="useroption", unique_together={("user", "name")} ), ] PKnO  -options/migrations/0003_auto_20190827_0605.py# Generated by Django 2.2.4 on 2019-08-27 11:05 from django.db import migrations, models import options.helpers class Migration(migrations.Migration): dependencies = [("options", "0002_auto_20181002_0254")] operations = [ migrations.AddField( model_name="option", name="file", field=models.FileField( blank=True, null=True, upload_to=options.helpers.UploadToDir("options", random_name=True), ), ), migrations.AddField( model_name="useroption", name="file", field=models.FileField( blank=True, null=True, upload_to=options.helpers.UploadToDir("options", random_name=True), ), ), migrations.AlterField( model_name="option", name="name", field=models.CharField( db_index=True, max_length=255, unique=True, verbose_name="parameter name", ), ), migrations.AlterField( model_name="option", name="public_name", field=models.CharField( db_index=True, max_length=255, verbose_name="public name of the parameter", ), ), migrations.AlterField( model_name="option", name="type", field=models.PositiveIntegerField( choices=[(0, "Float"), (1, "Integer"), (2, "String"), (3, "File")], default=2, ), ), migrations.AlterField( model_name="option", name="value", field=models.CharField( blank=True, default=None, max_length=256, null=True, verbose_name="value", ), ), migrations.AlterField( model_name="useroption", name="public_name", field=models.CharField( db_index=True, max_length=255, verbose_name="public name of the parameter", ), ), migrations.AlterField( model_name="useroption", name="type", field=models.PositiveIntegerField( choices=[(0, "Float"), (1, "Integer"), (2, "String"), (3, "File")], default=2, ), ), migrations.AlterField( model_name="useroption", name="value", field=models.CharField( blank=True, default=None, max_length=256, null=True, verbose_name="value", ), ), ] PKLNoptions/migrations/__init__.pyPKnOG0MM%options/rest_framework/serializers.pyfrom django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from options import get_option_model, get_user_option_model from options.settings import DEFAULT_EXCLUDE_USER Option = get_option_model() UserOption = get_user_option_model() class OptionSerializer(serializers.ModelSerializer): class Meta: model = Option fields = ["id", "name", "public_name", "type", "value", "file", "is_list"] class UserOptionSerializer(OptionSerializer): class Meta(OptionSerializer.Meta): model = UserOption def validate_name(self, value): """Checks if the name is in DEFAULT_EXCLUDE_USER_OPTIONS.""" if value in DEFAULT_EXCLUDE_USER: raise serializers.ValidationError( _("The name in the option can't be handle by the user.") ) PKnOQ?"options/rest_framework/viewsets.pyfrom rest_framework import viewsets from rest_framework.permissions import IsAdminUser, IsAuthenticated from options import get_option_model, get_user_option_model from options.rest_framework.serializers import OptionSerializer, UserOptionSerializer Option = get_option_model() UserOption = get_user_option_model() class OptionViewSet(viewsets.ModelViewSet): queryset = Option.objects.all() serializer_class = OptionSerializer permission_classes = (IsAdminUser,) class UserOptionViewSet(viewsets.ModelViewSet): queryset = UserOption.objects.filter_user_customizable() serializer_class = UserOptionSerializer permission_classes = (IsAuthenticated,) def get_queryset(self): queryset = super().get_queryset() return queryset.filter(user=self.request.user) def perform_create(self, serializer): serializer.save(user=self.request.user) PKLN88-django_simple_options-2.0.1.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_simple_options-2.0.1.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UrPK!H_.django_simple_options-2.0.1.dist-info/METADATATo0&ucMZi2 ڡk֏`#MbvX!R|޽s}<{?Q!NlS Y,=#R'a'iuW El)\sG_&] gd1U&h4baWe P/xy[=bf?| ەK2\nUÔİS+ ~nYMEbfIHmyXvpcR Iw2K A׸xځ^hjoopӝ|EA+f!8\c"]jC ]h)j󚨴w'eHE蚲ƅnr{nGZd8uye/l?J.<"Vyq[FDv C H}*,/ҡP ]J]xVED؎KAe$搌5[j<>MZ/!uey)A3Xc(=/}yIb|!4RQޑ:\d4cz؏\`Ȝeb^PQ*I 7nPAqUTEjUl2'^ӞF1n $*7QUkcSz~q?ufѤ;gErzrYsњ&sHnBK'v[ykBЄ?%حI4ef͓)|rS;%THD8聦|NOwK~N~SZPK!H=N,django_simple_options-2.0.1.dist-info/RECORDK8[wbr"  !j*AD~83^M*J=O{Ӵ=jj+IP$ym> /PM[hC7!YLg'Վ5`Eb㩗7*+T?q77i=q j{yh6pr49N'Ldl+Nt>+ {['&%$8cӺff~x`xfrbE{dt_Bla#Ϭ}vM1ngp;3K?/xxbf+ NHW7fCI,[cՌIfL"@m"jT'9{撀`{GUi?,uF5__軩2j["k6wfMjrX>=ɠ{_eEtÕ `E0Q }㧻X0 @£Ōp>ŭ3)A_ZUm՞!S2 jK+p[k3k*2Ƕ'ͺL@KT"s42,..< )EQgGiD^cJ[ǣhL]36($hIߠ$MP4YQ =Y*WzҗI˩t ؍*+#"QJ'zfsvc)b\"U;SQ-V28+{>9tioMWdbw{u_(zA If/ pw#Lǡa;UsN:q#oZʝ ˥ /9MQՖIz_s{Շc*bI{ \4( )w}YN,4li !7#ekhO QӒi6.;M!Лa2k[#DW f,I kdUUKXEڗ(q5vu)5,} ^PK!pO*+options/__init__.pyPKnOD55options/admin.pyPKnO%Voptions/apps.pyPKnOlaoptions/constants.pyPKiPN) options/context_processors.pyPK!pOR[  options/helpers.pyPKnOF|options/managers.pyPKnOCF Qoptions/models.pyPKnO O**(options/settings.pyPKiPNj,options/management/__init__.pyPKiPN',options/management/commands/__init__.pyPKiPN-,options/management/commands/export_options.pyPKiPN99"M/options/migrations/0001_initial.pyPKiPN -6options/migrations/0002_auto_20181002_0254.pyPKnO  -@options/migrations/0003_auto_20190827_0605.pyPKLNKoptions/migrations/__init__.pyPKnOG0MM%Koptions/rest_framework/serializers.pyPKnOQ?"ROoptions/rest_framework/viewsets.pyPKLN88-Sdjango_simple_options-2.0.1.dist-info/LICENSEPK!HPO+Wdjango_simple_options-2.0.1.dist-info/WHEELPK!H_.-Xdjango_simple_options-2.0.1.dist-info/METADATAPK!H=N,t[django_simple_options-2.0.1.dist-info/RECORDPK_