gm-148: refactored

This commit is contained in:
Anatoly 2019-10-02 10:16:32 +03:00
parent aeebdd02c6
commit d9635a8599
20 changed files with 241 additions and 54 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-10-02 06:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0008_auto_20190912_1325'),
]
operations = [
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(default=None, max_length=254, null=True, unique=True, verbose_name='email address'),
),
]

View File

@ -58,7 +58,7 @@ class User(AbstractUser):
blank=True, null=True, default=None) blank=True, null=True, default=None)
cropped_image_url = models.URLField(verbose_name=_('Cropped image URL path'), cropped_image_url = models.URLField(verbose_name=_('Cropped image URL path'),
blank=True, null=True, default=None) blank=True, null=True, default=None)
email = models.EmailField(_('email address'), blank=True, email = models.EmailField(_('email address'), unique=True,
null=True, default=None) null=True, default=None)
email_confirmed = models.BooleanField(_('email status'), default=False) email_confirmed = models.BooleanField(_('email status'), default=False)
newsletter = models.NullBooleanField(default=True) newsletter = models.NullBooleanField(default=True)

View File

@ -18,12 +18,6 @@ from utils.tokens import GMRefreshToken
# Serializers # Serializers
class SignupSerializer(serializers.ModelSerializer): class SignupSerializer(serializers.ModelSerializer):
"""Signup serializer serializer mixin""" """Signup serializer serializer mixin"""
# REQUEST
username = serializers.CharField(write_only=True)
password = serializers.CharField(write_only=True)
email = serializers.EmailField(write_only=True)
newsletter = serializers.BooleanField(write_only=True)
class Meta: class Meta:
model = account_models.User model = account_models.User
fields = ( fields = (
@ -32,6 +26,12 @@ class SignupSerializer(serializers.ModelSerializer):
'email', 'email',
'newsletter' 'newsletter'
) )
extra_kwargs = {
'username': {'write_only': True},
'password': {'write_only': True},
'email': {'write_only': True},
'newsletter': {'write_only': True}
}
def validate_username(self, value): def validate_username(self, value):
"""Custom username validation""" """Custom username validation"""

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2.4 on 2019-10-01 06:47
from django.db import migrations
import sorl.thumbnail.fields
import utils.methods
class Migration(migrations.Migration):
dependencies = [
('gallery', '0002_auto_20190930_0714'),
]
operations = [
migrations.AlterField(
model_name='image',
name='image',
field=sorl.thumbnail.fields.ImageField(upload_to=utils.methods.image_path, verbose_name='image file'),
),
]

View File

@ -1,16 +1,16 @@
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from easy_thumbnails.fields import ThumbnailerImageField from sorl.thumbnail.fields import ImageField as SORLImageField
from utils.methods import image_path from utils.methods import image_path
from utils.models import ProjectBaseMixin, ImageMixin, PlatformMixin from utils.models import ProjectBaseMixin, SORLImageMixin, PlatformMixin
class ImageQuerySet(models.QuerySet): class ImageQuerySet(models.QuerySet):
"""QuerySet for model Image.""" """QuerySet for model Image."""
class Image(ProjectBaseMixin, ImageMixin, PlatformMixin): class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
"""Image model.""" """Image model."""
HORIZONTAL = 0 HORIZONTAL = 0
VERTICAL = 1 VERTICAL = 1
@ -20,7 +20,7 @@ class Image(ProjectBaseMixin, ImageMixin, PlatformMixin):
(VERTICAL, _('Vertical')), (VERTICAL, _('Vertical')),
) )
image = ThumbnailerImageField(upload_to=image_path, image = SORLImageField(upload_to=image_path,
verbose_name=_('image file')) verbose_name=_('image file'))
parent = models.ForeignKey('self', parent = models.ForeignKey('self',
blank=True, null=True, default=None, blank=True, null=True, default=None,

View File

@ -8,7 +8,6 @@ class ImageSerializer(serializers.ModelSerializer):
# REQUEST # REQUEST
file = serializers.ImageField(source='image', file = serializers.ImageField(source='image',
write_only=True) write_only=True)
title = serializers.CharField()
orientation = serializers.ChoiceField(choices=models.Image.ORIENTATIONS, orientation = serializers.ChoiceField(choices=models.Image.ORIENTATIONS,
write_only=True) write_only=True)
@ -21,7 +20,7 @@ class ImageSerializer(serializers.ModelSerializer):
class Meta: class Meta:
"""Meta class""" """Meta class"""
model = models.Image model = models.Image
fields = ( fields = [
'id', 'id',
'file', 'file',
'url', 'url',
@ -29,5 +28,4 @@ class ImageSerializer(serializers.ModelSerializer):
'orientation', 'orientation',
'orientation_display', 'orientation_display',
'title', 'title',
) ]

View File

@ -0,0 +1,14 @@
# Generated by Django 2.2.4 on 2019-09-30 12:51
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('news', '0019_news_author'),
('news', '0016_news_gallery'),
]
operations = [
]

View File

@ -2,6 +2,7 @@
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from account.serializers.common import UserSerializer
from gallery.models import Image from gallery.models import Image
from gallery.serializers import ImageSerializer from gallery.serializers import ImageSerializer
from location import models as location_models from location import models as location_models
@ -9,7 +10,26 @@ from location.serializers import CountrySimpleSerializer
from main.serializers import MetaDataContentSerializer from main.serializers import MetaDataContentSerializer
from news import models from news import models
from utils.serializers import TranslatedField from utils.serializers import TranslatedField
from account.serializers.common import UserSerializer
class NewsImageSerializer(ImageSerializer):
"""News images"""
promo_web_url = serializers.SerializerMethodField()
promo_mobile_url = serializers.SerializerMethodField()
class Meta:
model = Image
fields = ImageSerializer.Meta.fields + [
'promo_web_url',
'promo_mobile_url'
]
def get_promo_web_url(self, obj):
return obj.get_image_url(thumbnail_key='news_promo_horizontal_web')
def get_promo_mobile_url(self, obj):
return obj.get_image_url(thumbnail_key='news_promo_horizontal_mobile')
class NewsTypeSerializer(serializers.ModelSerializer): class NewsTypeSerializer(serializers.ModelSerializer):
"""News type serializer.""" """News type serializer."""
@ -31,7 +51,7 @@ class NewsBaseSerializer(serializers.ModelSerializer):
# related fields # related fields
news_type = NewsTypeSerializer(read_only=True) news_type = NewsTypeSerializer(read_only=True)
tags = MetaDataContentSerializer(read_only=True, many=True) tags = MetaDataContentSerializer(read_only=True, many=True)
gallery = ImageSerializer(read_only=True, many=True) gallery = NewsImageSerializer(read_only=True, many=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -115,14 +135,11 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): class NewsBackOfficeGallerySerializer(serializers.ModelSerializer):
"""Serializer class for model NewsGallery.""" """Serializer class for model NewsGallery."""
image = ImageSerializer(read_only=True)
class Meta: class Meta:
"""Meta class""" """Meta class"""
model = models.NewsGallery model = models.NewsGallery
fields = [ fields = [
'id', 'id',
'image',
] ]
def get_request_kwargs(self): def get_request_kwargs(self):

View File

@ -1,8 +1,8 @@
"""News app views.""" """News app views."""
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics, permissions from rest_framework import generics, permissions, status
from rest_framework.response import Response
from gallery.serializers import ImageSerializer
from news import filters, models, serializers from news import filters, models, serializers
@ -82,10 +82,15 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView,
return gallery return gallery
def create(self, request, *args, **kwargs):
"""Override create method"""
super().create(request, *args, **kwargs)
return Response(status=status.HTTP_201_CREATED)
class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIView): class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIView):
"""Resource for returning gallery for news for back-office users.""" """Resource for returning gallery for news for back-office users."""
serializer_class = ImageSerializer serializer_class = serializers.NewsImageSerializer
def get_object(self): def get_object(self):
"""Override get_object method.""" """Override get_object method."""

View File

@ -1,4 +1,5 @@
"""Utils app method.""" """Utils app method."""
import logging
import random import random
import re import re
import string import string
@ -10,6 +11,9 @@ from django.http.request import HttpRequest
from django.utils.timezone import datetime from django.utils.timezone import datetime
from rest_framework import status from rest_framework import status
from rest_framework.request import Request from rest_framework.request import Request
from os.path import exists
logger = logging.getLogger(__name__)
def generate_code(digits=6, string_output=True): def generate_code(digits=6, string_output=True):
@ -91,12 +95,18 @@ def get_contenttype(app_label: str, model: str):
def image_url_valid(url: str): def image_url_valid(url: str):
# In case if image storage is not on CDN """
if url.startswith('/media/'): Check if requested URL is valid.
url = f'{settings.SCHEMA_URI}://{settings.DOMAIN_URI}{url}' :param url: string
:return: boolean
"""
try:
assert url.startswith('http')
response = requests.request('head', url) response = requests.request('head', url)
if response.status_code == status.HTTP_200_OK: except Exception as e:
return True logger.info(f'ConnectionError: {e}')
else:
return response.status_code == status.HTTP_200_OK
def absolute_url_decorator(func): def absolute_url_decorator(func):
@ -105,7 +115,7 @@ def absolute_url_decorator(func):
url_path = func(self, obj) url_path = func(self, obj)
if url_path: if url_path:
if url_path.startswith('/media/'): if url_path.startswith('/media/'):
return f'{settings.SCHEMA_URI}://{settings.DOMAIN_URI}{url_path}/' return f'{settings.MEDIA_URL}{url_path}/'
else: else:
return url_path return url_path
return get_absolute_image_url return get_absolute_image_url

View File

@ -1,4 +1,5 @@
"""Utils app models.""" """Utils app models."""
import logging
from os.path import exists from os.path import exists
from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.contrib.gis.db import models from django.contrib.gis.db import models
@ -10,8 +11,12 @@ from django.utils.translation import ugettext_lazy as _, get_language
from easy_thumbnails.fields import ThumbnailerImageField from easy_thumbnails.fields import ThumbnailerImageField
from utils.methods import image_path, svg_image_path from utils.methods import image_path, svg_image_path
from utils.validators import svg_image_validator from utils.validators import svg_image_validator
from django.db.models.fields import Field from sorl.thumbnail.fields import ImageField as SORLImageField
from django.core import exceptions from sorl.thumbnail import get_thumbnail
from django.conf import settings
from utils.methods import image_url_valid
logger = logging.getLogger(__name__)
class ProjectBaseMixin(models.Model): class ProjectBaseMixin(models.Model):
@ -177,6 +182,38 @@ class ImageMixin(models.Model):
image_tag.allow_tags = True image_tag.allow_tags = True
class SORLImageMixin(models.Model):
"""Abstract model for SORL ImageField"""
image = SORLImageField(upload_to=image_path,
blank=True, null=True, default=None,
verbose_name=_('Image'))
class Meta:
"""Meta class."""
abstract = True
def get_image(self, thumbnail_key=None):
"""Get thumbnail image file."""
if thumbnail_key in settings.SORL_THUMBNAIL_ALIASES:
return get_thumbnail(file_=self.image,
**settings.SORL_THUMBNAIL_ALIASES[thumbnail_key])
def get_image_url(self, thumbnail_key=None):
"""Get image thumbnail url."""
return self.get_image(thumbnail_key).url
def image_tag(self):
"""Admin preview tag."""
if self.image:
return mark_safe('<img src="%s" />' % self.image.url)
else:
return None
image_tag.short_description = _('Image')
image_tag.allow_tags = True
class SVGImageMixin(models.Model): class SVGImageMixin(models.Model):
"""SVG image model.""" """SVG image model."""

View File

@ -0,0 +1,19 @@
"""Settings for Amazon S3"""
import os
from .base import MEDIA_LOCATION
# AMAZON S3
AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME')
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'}
# Static settings
# PUBLIC_STATIC_LOCATION = 'static'
# STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{PUBLIC_STATIC_LOCATION}/'
# STATICFILES_STORAGE = 'project.storage_backends.PublicStaticStorage'
# Public media settings
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{MEDIA_LOCATION}/'
DEFAULT_FILE_STORAGE = 'project.storage_backends.PublicMediaStorage'

View File

@ -91,6 +91,8 @@ EXTERNAL_APPS = [
'rest_framework_simplejwt.token_blacklist', 'rest_framework_simplejwt.token_blacklist',
'solo', 'solo',
'phonenumber_field', 'phonenumber_field',
'storages',
'sorl.thumbnail',
] ]
@ -190,19 +192,6 @@ LOCALE_PATHS = (
os.path.abspath(os.path.join(BASE_DIR, 'locale')), os.path.abspath(os.path.join(BASE_DIR, 'locale')),
) )
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_ROOT = os.path.join(PUBLIC_ROOT, 'static')
STATIC_URL = '/static/'
MEDIA_ROOT = os.path.join(PUBLIC_ROOT, 'media')
MEDIA_URL = '/media/'
STATICFILES_DIRS = (
os.path.join(PROJECT_ROOT, 'static'),
)
AVAILABLE_VERSIONS = { AVAILABLE_VERSIONS = {
# 'future': '1.0.1', # 'future': '1.0.1',
'current': '1.0.0', 'current': '1.0.0',
@ -262,6 +251,7 @@ SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
'fields': 'id, name, email', 'fields': 'id, name, email',
} }
# SMS Settings # SMS Settings
SMS_EXPIRATION = 5 SMS_EXPIRATION = 5
SMS_SEND_DELAY = 30 SMS_SEND_DELAY = 30
@ -272,12 +262,14 @@ SMS_CODE_LENGTH = 6
SEND_SMS = True SEND_SMS = True
SMS_CODE_SHOW = False SMS_CODE_SHOW = False
# SMSC Settings # SMSC Settings
SMS_SERVICE = 'http://smsc.ru/sys/send.php' SMS_SERVICE = 'http://smsc.ru/sys/send.php'
SMS_LOGIN = 'GM2019' SMS_LOGIN = 'GM2019'
SMS_PASSWORD = '}#6%Qe7CYG7n' SMS_PASSWORD = '}#6%Qe7CYG7n'
SMS_SENDER = 'GM' SMS_SENDER = 'GM'
# EMAIL # EMAIL
EMAIL_USE_TLS = True EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST = 'smtp.gmail.com'
@ -285,6 +277,7 @@ EMAIL_HOST_USER = 'anatolyfeteleu@gmail.com'
EMAIL_HOST_PASSWORD = 'nggrlnbehzksgmbt' EMAIL_HOST_PASSWORD = 'nggrlnbehzksgmbt'
EMAIL_PORT = 587 EMAIL_PORT = 587
# Django Rest Swagger # Django Rest Swagger
SWAGGER_SETTINGS = { SWAGGER_SETTINGS = {
# "DEFAULT_GENERATOR_CLASS": "rest_framework.schemas.generators.BaseSchemaGenerator", # "DEFAULT_GENERATOR_CLASS": "rest_framework.schemas.generators.BaseSchemaGenerator",
@ -307,6 +300,7 @@ REDOC_SETTINGS = {
'LAZY_RENDERING': False, 'LAZY_RENDERING': False,
} }
# CELERY # CELERY
BROKER_URL = 'amqp://rabbitmq:5672' BROKER_URL = 'amqp://rabbitmq:5672'
CELERY_RESULT_BACKEND = BROKER_URL CELERY_RESULT_BACKEND = BROKER_URL
@ -316,6 +310,7 @@ CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = TIME_ZONE CELERY_TIMEZONE = TIME_ZONE
# Django FCM (Firebase push notifications) # Django FCM (Firebase push notifications)
FCM_DJANGO_SETTINGS = { FCM_DJANGO_SETTINGS = {
'FCM_SERVER_KEY': ( 'FCM_SERVER_KEY': (
@ -325,6 +320,7 @@ FCM_DJANGO_SETTINGS = {
), ),
} }
# Thumbnail settings # Thumbnail settings
THUMBNAIL_ALIASES = { THUMBNAIL_ALIASES = {
'': { '': {
@ -345,11 +341,23 @@ THUMBNAIL_DEFAULT_OPTIONS = {
'crop': 'smart', 'crop': 'smart',
} }
# Password reset
RESETTING_TOKEN_EXPIRATION = 24 # hours
# SORL
THUMBNAIL_QUALITY = 85
THUMBNAIL_DEBUG = False
SORL_THUMBNAIL_ALIASES = {
'news_preview': {'geometry_string': '100x100', 'crop': 'center'},
'news_promo_horizontal_web': {'geometry_string': '1900x600', 'crop': 'center'},
'news_promo_horizontal_mobile': {'geometry_string': '375x260', 'crop': 'center'},
'news_tile_horizontal_web': {'geometry_string': '300x275', 'crop': 'center'},
'news_tile_horizontal_mobile': {'geometry_string': '343x180', 'crop': 'center'},
'news_tile_vertical_web': {'geometry_string': '300x380', 'crop': 'center'},
'news_highlight_vertical_web': {'geometry_string': '460x630', 'crop': 'center'},
'news_editor_web': {'geometry_string': '940x430', 'crop': 'center'},
'news_editor_mobile': {'geometry_string': '343x260', 'crop': 'center'}, # при загрузке через контент эдитор
'avatar_comments_web': {'geometry_string': '116x116', 'crop': 'center'}, # через контент эдитор в мобильном браузерe
}
GEOIP_PATH = os.path.join(PROJECT_ROOT, 'geoip_db')
# JWT # JWT
SIMPLE_JWT = { SIMPLE_JWT = {
@ -409,17 +417,20 @@ DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5MB
FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5MB FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5MB
FILE_UPLOAD_PERMISSIONS = 0o644 FILE_UPLOAD_PERMISSIONS = 0o644
# SOLO SETTINGS # SOLO SETTINGS
# todo: make a separate service (redis?) and update solo_cache # todo: make a separate service (redis?) and update solo_cache
SOLO_CACHE = 'default' SOLO_CACHE = 'default'
SOLO_CACHE_PREFIX = 'solo' SOLO_CACHE_PREFIX = 'solo'
SOLO_CACHE_TIMEOUT = 300 SOLO_CACHE_TIMEOUT = 300
# REDIRECT URL # REDIRECT URL
SITE_REDIRECT_URL_UNSUBSCRIBE = '/unsubscribe/' SITE_REDIRECT_URL_UNSUBSCRIBE = '/unsubscribe/'
SITE_NAME = 'Gault & Millau' SITE_NAME = 'Gault & Millau'
# Used in annotations for establishments. # Used in annotations for establishments.
DEFAULT_ESTABLISHMENT_PUBLIC_MARK = 10 DEFAULT_ESTABLISHMENT_PUBLIC_MARK = 10
# Limit output objects (see in pagination classes). # Limit output objects (see in pagination classes).
@ -427,6 +438,19 @@ QUERY_OUTPUT_OBJECTS = 12
# Need to restrict objects to sort (3 times more then expected). # Need to restrict objects to sort (3 times more then expected).
LIMITING_QUERY_OBJECTS = QUERY_OUTPUT_OBJECTS * 3 LIMITING_QUERY_OBJECTS = QUERY_OUTPUT_OBJECTS * 3
# GEO # GEO
# A Spatial Reference System Identifier # A Spatial Reference System Identifier
GEO_DEFAULT_SRID = 4326 GEO_DEFAULT_SRID = 4326
GEOIP_PATH = os.path.join(PROJECT_ROOT, 'geoip_db')
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_ROOT = os.path.join(PUBLIC_ROOT, 'static')
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(PROJECT_ROOT, 'static'),
)
MEDIA_LOCATION = 'media'

View File

@ -1,9 +1,10 @@
"""Development settings.""" """Development settings."""
from .base import * from .base import *
from .amazon_s3 import *
import sentry_sdk import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.django import DjangoIntegration
ALLOWED_HOSTS = ['gm.id-east.ru', '95.213.204.126'] ALLOWED_HOSTS = ['gm.id-east.ru', '95.213.204.126', '0.0.0.0']
SEND_SMS = False SEND_SMS = False
SMS_CODE_SHOW = True SMS_CODE_SHOW = True

View File

@ -17,6 +17,8 @@ BROKER_URL = 'amqp://rabbitmq:5672'
CELERY_RESULT_BACKEND = BROKER_URL CELERY_RESULT_BACKEND = BROKER_URL
CELERY_BROKER_URL = BROKER_URL CELERY_BROKER_URL = BROKER_URL
MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION)
MEDIA_URL = f'{SCHEMA_URI}://{DOMAIN_URI}/{MEDIA_LOCATION}/'
# LOGGING # LOGGING
LOGGING = { LOGGING = {
@ -62,8 +64,10 @@ ELASTICSEARCH_DSL = {
} }
} }
ELASTICSEARCH_INDEX_NAMES = { ELASTICSEARCH_INDEX_NAMES = {
# 'search_indexes.documents.news': 'local_news', # 'search_indexes.documents.news': 'local_news',
'search_indexes.documents.establishment': 'local_establishment', 'search_indexes.documents.establishment': 'local_establishment',
} }
# SORL thumbnails
THUMBNAIL_DEBUG = True

View File

@ -1,2 +1,3 @@
"""Production settings.""" """Production settings."""
from .base import * from .base import *
from .amazon_s3 import *

View File

@ -1,5 +1,6 @@
"""Stage settings.""" """Stage settings."""
from .base import * from .base import *
from .amazon_s3 import *
ALLOWED_HOSTS = ['gm-stage.id-east.ru', '95.213.204.126'] ALLOWED_HOSTS = ['gm-stage.id-east.ru', '95.213.204.126']

View File

@ -0,0 +1,12 @@
"""Extend storage backend for adding custom parameters"""
from storages.backends.s3boto3 import S3Boto3Storage
class PublicMediaStorage(S3Boto3Storage):
location = 'media'
file_overwrite = False
class PublicStaticStorage(S3Boto3Storage):
location = 'static'
file_overwrite = False

View File

@ -64,7 +64,7 @@ urlpatterns = [
] ]
urlpatterns = urlpatterns + \ urlpatterns = urlpatterns + \
static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) static(settings.MEDIA_LOCATION, document_root=settings.MEDIA_ROOT)
if settings.DEBUG: if settings.DEBUG:
urlpatterns.extend(urlpatterns_doc) urlpatterns.extend(urlpatterns_doc)

View File

@ -33,3 +33,9 @@ djangorestframework-simplejwt==4.3.0
django-elasticsearch-dsl>=7.0.0,<8.0.0 django-elasticsearch-dsl>=7.0.0,<8.0.0
django-elasticsearch-dsl-drf==0.20.2 django-elasticsearch-dsl-drf==0.20.2
sentry-sdk==0.11.2 sentry-sdk==0.11.2
# AMAZON S3
boto3==1.9.238
django-storages==1.7.2
sorl-thumbnail==12.5.0