From e6b514e71c8890e711e9ca190fb62ffd0680d83c Mon Sep 17 00:00:00 2001 From: Ivlev Denis Date: Mon, 26 Mar 2018 13:38:20 +0300 Subject: [PATCH] Remove constance & use singleton model for dynamc settings --- api/v1/serializers/config.py | 43 ++++++------------- api/v1/views.py | 19 +++----- apps/config/__init__.py | 0 apps/config/admin.py | 3 ++ apps/config/apps.py | 5 +++ apps/config/migrations/0001_initial.py | 27 ++++++++++++ .../migrations/0002_auto_20180326_1026.py | 18 ++++++++ .../migrations/0003_auto_20180326_1027.py | 23 ++++++++++ apps/config/migrations/__init__.py | 0 apps/config/models.py | 25 +++++++++++ apps/config/tests.py | 3 ++ apps/config/views.py | 3 ++ apps/content/tasks.py | 5 ++- apps/payment/models.py | 4 +- project/context_processors.py | 5 +++ project/fields.py | 14 ++++++ project/settings.py | 41 ++---------------- requirements.txt | 1 - 18 files changed, 155 insertions(+), 84 deletions(-) create mode 100644 apps/config/__init__.py create mode 100644 apps/config/admin.py create mode 100644 apps/config/apps.py create mode 100644 apps/config/migrations/0001_initial.py create mode 100644 apps/config/migrations/0002_auto_20180326_1026.py create mode 100644 apps/config/migrations/0003_auto_20180326_1027.py create mode 100644 apps/config/migrations/__init__.py create mode 100644 apps/config/models.py create mode 100644 apps/config/tests.py create mode 100644 apps/config/views.py create mode 100644 project/context_processors.py create mode 100644 project/fields.py diff --git a/api/v1/serializers/config.py b/api/v1/serializers/config.py index f6ae89dd..c2234a13 100644 --- a/api/v1/serializers/config.py +++ b/api/v1/serializers/config.py @@ -1,40 +1,25 @@ -from constance import config -from constance.admin import get_values, ConstanceForm from rest_framework import serializers -from rest_framework.fields import SkipField -from collections import OrderedDict +from apps.config.models import Config -def _set_constance_value(key, value): - form = ConstanceForm(initial=get_values()) - field = form.fields[key] - clean_value = field.clean(field.to_python(value)) - setattr(config, key, clean_value) - -class ConfigSerializer(serializers.Serializer): +class ConfigSerializer(serializers.ModelSerializer): SERVICE_COMMISSION = serializers.IntegerField(required=False) SERVICE_DISCOUNT_MIN_AMOUNT = serializers.IntegerField(required=False) SERVICE_DISCOUNT = serializers.IntegerField(required=False) INSTAGRAM_CLIENT_ACCESS_TOKEN = serializers.CharField(required=False) INSTAGRAM_CLIENT_SECRET = serializers.CharField(required=False) INSTAGRAM_PROFILE_URL = serializers.CharField(required=False) - # SCHOOL_LOGO_IMAGE = serializers.ImageField(required=False) - - def to_representation(self, instance): - ret = OrderedDict() - fields = self._readable_fields - for field in fields: - attribute = instance.get(field.field_name) - ret[field.field_name] = field.to_representation(attribute) - return ret - - def to_internal_value(self, data): - ret = OrderedDict(get_values()) - for k, v in data.items(): - ret[k] = v - return ret + SCHOOL_LOGO_IMAGE = serializers.ImageField(required=False) - def update(self, instance, validated_data): - for k, v in validated_data.items(): - _set_constance_value(k, v) + class Meta: + model = Config + fields = ( + 'SERVICE_COMMISSION', + 'SERVICE_DISCOUNT_MIN_AMOUNT', + 'SERVICE_DISCOUNT', + 'INSTAGRAM_CLIENT_ACCESS_TOKEN', + 'INSTAGRAM_CLIENT_SECRET', + 'INSTAGRAM_PROFILE_URL', + 'SCHOOL_LOGO_IMAGE', + ) diff --git a/api/v1/views.py b/api/v1/views.py index 909195d4..c47ce413 100644 --- a/api/v1/views.py +++ b/api/v1/views.py @@ -1,10 +1,6 @@ -from constance.admin import get_values - from django.contrib.auth import get_user_model -from rest_framework import status -from rest_framework import views, viewsets -from rest_framework import generics +from rest_framework import status, views, viewsets, generics from rest_framework.decorators import detail_route, list_route from rest_framework.response import Response @@ -43,6 +39,7 @@ from apps.course.models import ( Material, Lesson, Like, ) +from apps.config.models import Config from apps.content.models import ( Image, Text, ImageText, Video, Gallery, GalleryImage, ImageObject, @@ -327,18 +324,12 @@ class SchoolScheduleViewSet(ExtendedModelViewSet): class ConfigViewSet(generics.RetrieveUpdateAPIView): + queryset = Config.objects.all() serializer_class = ConfigSerializer permission_classes = (IsAdmin,) - def retrieve(self, request, *args, **kwargs): - serializer = ConfigSerializer(get_values()) - return Response(serializer.data) - - def patch(self, request, *args, **kwargs): - serializer = ConfigSerializer(data=request.data) - if serializer.is_valid(): - serializer.update(get_values(), serializer.validated_data) - return Response(serializer.data) + def get_object(self): + return Config.load() class CommentViewSet(ExtendedModelViewSet): diff --git a/apps/config/__init__.py b/apps/config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/config/admin.py b/apps/config/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/apps/config/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/config/apps.py b/apps/config/apps.py new file mode 100644 index 00000000..8d9481b1 --- /dev/null +++ b/apps/config/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ConfigConfig(AppConfig): + name = 'config' diff --git a/apps/config/migrations/0001_initial.py b/apps/config/migrations/0001_initial.py new file mode 100644 index 00000000..0aae28c7 --- /dev/null +++ b/apps/config/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 2.0.3 on 2018-03-26 10:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Config', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('INSTAGRAM_CLIENT_ACCESS_TOKEN', models.CharField(default='7145314808.f6fa114.6b737a5355534e0eb5cf7c40cb4998f6', max_length=51)), + ('INSTAGRAM_CLIENT_SECRET', models.CharField(default='2334a921425140ccb180d145dcd35b25', max_length=32)), + ('INSTAGRAM_PROFILE_URL', models.CharField(default='#', max_length=126)), + ('SERVICE_COMMISSION', models.IntegerField(default=10)), + ('SERVICE_DISCOUNT_MIN_AMOUNT', models.IntegerField(default=3500)), + ('SERVICE_DISCOUNT', models.ImageField(default=1000, upload_to='')), + ('SCHOOL_LOGO_IMAGE', models.ImageField(null=True, upload_to='')), + ], + ), + ] diff --git a/apps/config/migrations/0002_auto_20180326_1026.py b/apps/config/migrations/0002_auto_20180326_1026.py new file mode 100644 index 00000000..b91b4812 --- /dev/null +++ b/apps/config/migrations/0002_auto_20180326_1026.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.3 on 2018-03-26 10:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('config', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='config', + name='SCHOOL_LOGO_IMAGE', + field=models.FileField(null=True, upload_to=''), + ), + ] diff --git a/apps/config/migrations/0003_auto_20180326_1027.py b/apps/config/migrations/0003_auto_20180326_1027.py new file mode 100644 index 00000000..19d5c208 --- /dev/null +++ b/apps/config/migrations/0003_auto_20180326_1027.py @@ -0,0 +1,23 @@ +# Generated by Django 2.0.3 on 2018-03-26 10:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('config', '0002_auto_20180326_1026'), + ] + + operations = [ + migrations.AlterField( + model_name='config', + name='SCHOOL_LOGO_IMAGE', + field=models.ImageField(null=True, upload_to=''), + ), + migrations.AlterField( + model_name='config', + name='SERVICE_DISCOUNT', + field=models.IntegerField(default=1000), + ), + ] diff --git a/apps/config/migrations/__init__.py b/apps/config/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/config/models.py b/apps/config/models.py new file mode 100644 index 00000000..385717f5 --- /dev/null +++ b/apps/config/models.py @@ -0,0 +1,25 @@ +from django.db import models + + +class Config(models.Model): + INSTAGRAM_CLIENT_ACCESS_TOKEN = models.CharField( + max_length=51, default='7145314808.f6fa114.6b737a5355534e0eb5cf7c40cb4998f6' + ) + INSTAGRAM_CLIENT_SECRET = models.CharField(max_length=32, default='2334a921425140ccb180d145dcd35b25') + INSTAGRAM_PROFILE_URL = models.CharField(max_length=126, default='#') + SERVICE_COMMISSION = models.IntegerField(default=10) + SERVICE_DISCOUNT_MIN_AMOUNT = models.IntegerField(default=3500) + SERVICE_DISCOUNT = models.IntegerField(default=1000) + SCHOOL_LOGO_IMAGE = models.ImageField(null=True) + + def save(self, *args, **kwargs): + self.pk = 1 + super().save(*args, **kwargs) + + def delete(self, *args, **kwargs): + pass + + @classmethod + def load(cls): + obj, created = cls.objects.get_or_create(pk=1) + return obj diff --git a/apps/config/tests.py b/apps/config/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/config/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/config/views.py b/apps/config/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/apps/config/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/content/tasks.py b/apps/content/tasks.py index 9f58dcaa..ad2699ce 100644 --- a/apps/content/tasks.py +++ b/apps/content/tasks.py @@ -3,13 +3,16 @@ import json import requests import shutil -from constance import config from instagram.client import InstagramAPI from project.celery import app from time import sleep from django.conf import settings +from apps.config.models import Config + +config = Config.load() + @app.task def retrieve_photos(): diff --git a/apps/payment/models.py b/apps/payment/models.py index d3b42c8c..8f1f36d8 100644 --- a/apps/payment/models.py +++ b/apps/payment/models.py @@ -1,4 +1,3 @@ -from constance import config from paymentwall import Pingback from polymorphic.models import PolymorphicModel @@ -9,9 +8,12 @@ from django.core.validators import RegexValidator from django.utils.timezone import now from apps.course.models import Course +from apps.config.models import Config from apps.school.models import SchoolSchedule from apps.notification.utils import send_email + +config = Config.load() User = get_user_model() CREDIT_CARD_RE = r'^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\d{11})$' diff --git a/project/context_processors.py b/project/context_processors.py new file mode 100644 index 00000000..cf8dc47a --- /dev/null +++ b/project/context_processors.py @@ -0,0 +1,5 @@ +from apps.config.models import Config + + +def config(request): + return {"config": Config.load()} diff --git a/project/fields.py b/project/fields.py new file mode 100644 index 00000000..a2e4709f --- /dev/null +++ b/project/fields.py @@ -0,0 +1,14 @@ +from django.forms import ImageField as BaseImageField + + +class ImageField(BaseImageField): + + def to_internal_value(self, data): + # if data is None image field was not uploaded + if data: + file_object = super(ImageField, self).to_internal_value(data) + django_field = self._DjangoImageField() + django_field.error_messages = self.error_messages + django_field.to_python(file_object) + return file_object + return data diff --git a/project/settings.py b/project/settings.py index ff39ee1b..472f3da2 100644 --- a/project/settings.py +++ b/project/settings.py @@ -17,7 +17,6 @@ from celery.schedules import crontab from collections import OrderedDict from datetime import timedelta - # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -53,8 +52,6 @@ INSTALLED_APPS = [ 'rest_framework.authtoken', 'drf_yasg', 'corsheaders', - 'constance', - 'constance.backends.database', 'sorl.thumbnail', 'raven.contrib.django.raven_compat', ] + [ @@ -64,6 +61,7 @@ INSTALLED_APPS = [ 'apps.payment', 'apps.course', 'apps.content', + 'apps.config', 'apps.school', ] @@ -90,7 +88,7 @@ TEMPLATES = [ 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ - 'constance.context_processors.config', + 'project.context_processors.config', 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', @@ -204,6 +202,7 @@ REST_FRAMEWORK = { ), 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', + 'rest_framework.renderers.BrowsableAPIRenderer', ), 'DEFAULT_FILTER_BACKENDS': ( 'django_filters.rest_framework.DjangoFilterBackend', @@ -228,40 +227,6 @@ CELERY_BEAT_SCHEDULE = { }, } -# Dynamic settings - -CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' -CONSTANCE_ADDITIONAL_FIELDS = { - 'image_field': ['django.forms.ImageField', {}] -} -CONSTANCE_CONFIG = OrderedDict(( - ('INSTAGRAM_CLIENT_ACCESS_TOKEN', ('7145314808.f6fa114.6b737a5355534e0eb5cf7c40cb4998f6', '')), - ('INSTAGRAM_CLIENT_SECRET', ('2334a921425140ccb180d145dcd35b25', '')), - ('INSTAGRAM_PROFILE_URL', ('#', 'URL профиля Instagram.')), - ('INSTAGRAM_RESULTS_TAG', ('#lil_акварель', 'Тэг результатов работ.')), - ('INSTAGRAM_RESULTS_PATH', ('media/instagram/results/', 'Путь до результатов работ.')), - ('SERVICE_COMMISSION', (10, 'Комиссия сервиса в процентах.')), - ('SERVICE_DISCOUNT_MIN_AMOUNT', (3500, 'Минимальная сумма платежа для школы, после которой вычитывается скидка SERVICE_DISCOUNT.')), - ('SERVICE_DISCOUNT', (1000, 'Комиссия сервиса при покупке всех дней.')), - # ('SCHOOL_LOGO_IMAGE', ('default.png', 'Изображение в диалоге покупки школы', 'image_field')), -)) - -CONSTANCE_CONFIG_FIELDSETS = OrderedDict({ - 'Service': ( - 'SERVICE_COMMISSION', - 'SERVICE_DISCOUNT_MIN_AMOUNT', - 'SERVICE_DISCOUNT', - # 'SCHOOL_LOGO_IMAGE', - ), - 'Instagram': ( - 'INSTAGRAM_CLIENT_ACCESS_TOKEN', - 'INSTAGRAM_CLIENT_SECRET', - 'INSTAGRAM_PROFILE_URL', - 'INSTAGRAM_RESULTS_TAG', - 'INSTAGRAM_RESULTS_PATH', - ), -}) - try: from paymentwall import * except ImportError: diff --git a/requirements.txt b/requirements.txt index 710cf88d..a84ea848 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ Django==2.0.3 django-active-link==0.1.2 django-anymail[mailgun]==2.0 django-cors-headers==2.2.0 -django-constance[database]==2.1.0 django-filter==2.0.0.dev1 django-mptt==0.9.0 django-silk==2.0.0