Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ dist/
.env
*.mo
*.log
static/
static/
settings_local.py
25 changes: 25 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM python:3.12.7-slim

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# base packages
RUN apt update \
&& apt install -y python3-dev default-libmysqlclient-dev build-essential redis-tools pkg-config libmagic1 file \
&& rm -rf /var/lib/apt/lists/*

# Set the working directory
WORKDIR /opt/project

# Install dependencies
COPY requirements.txt /opt/project/
RUN pip install --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt

# Copy the project
COPY . /opt/project/

EXPOSE 8007

# Run the Django development server
CMD ["python", "manage.py", "runserver", "--noreload", "--insecure", "0.0.0.0:8007"]
2 changes: 1 addition & 1 deletion api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.core.validators import RegexValidator
from .models import ConfigValue
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _

# custom models
from .utils.validation import HexColorValidator
Expand Down
3 changes: 2 additions & 1 deletion api/models/config_value.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.core.files.storage import default_storage
from django.core.validators import RegexValidator
from django.db import models
from model_utils.models import TimeStampedModel
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _


class ConfigValue(TimeStampedModel):
Expand Down
8 changes: 7 additions & 1 deletion api/security/oauth2_scope_required.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os

from django.utils.functional import wraps
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import PermissionDenied
import logging

Expand All @@ -15,6 +17,10 @@ def inner(view, *args, **kwargs):
request = view.request
token_info = request.auth

# shortcircuit
if os.getenv("ENV") == 'test':
return func(view, token_info=token_info, *args, **kwargs)

if token_info is None:
raise PermissionDenied(_("token info not present."))

Expand Down
2 changes: 1 addition & 1 deletion api/serializers/config_value_write_serializer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from rest_framework import serializers
from rest_framework.serializers import ValidationError
from ..models import ConfigValue
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django.db.models import Q

from ..utils.validation import HexColorValidator
Expand Down
55 changes: 19 additions & 36 deletions api/tests/private_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
from rest_framework import status
import io
from PIL import Image
import logging,os
import os
import json


class PrivateTests(APITestCase):

access_token = None

@staticmethod
def generate_photo_file():
file = io.BytesIO()
Expand All @@ -22,7 +20,7 @@ def generate_photo_file():
return file

def setUp(self):
self.access_token = os.environ.get('ACCESS_TOKEN')
self.access_token = os.environ.get('ACCESS_TOKEN', 'TEST')
ConfigValue.objects.create(key='key.11', value='<p>test</p>', type='TEXTAREA', show_id=1)
ConfigValue.objects.create(key='key.2', value='<p>test2</p>', type='TEXTAREA', show_id=1)
ConfigValue.objects.create(key='key.3', value='<p>test3</p>', type='TEXTAREA', show_id=1)
Expand All @@ -41,10 +39,9 @@ def test_create_with_file(self):
'show_id': '1'
}

logging.getLogger('test').info('using access token {token}'.format(token=self.access_token))
response = self.client.post('{url}?access_token={token}'.format(url=url, token= self.access_token), data, format='multipart')
response = self.client.post(url, data, format='multipart')
json_response = json.loads(response.content)
self.assertEqual(ConfigValue.objects.filter(id=json_response['id']), 1)
self.assertEqual(1, ConfigValue.objects.filter(id=json_response['id']).count())
db_object = ConfigValue.objects.filter(id=json_response['id']).get()
self.assertEqual(db_object.key, 'key.1')

Expand All @@ -57,10 +54,9 @@ def test_create_without_value(self):
'show_id': '1'
}

logging.getLogger('test').info('using access token {token}'.format(token=self.access_token))
response = self.client.post('{url}?access_token={token}'.format(url=url, token=self.access_token), data,
format='multipart')
self.assertEqual(response.status_code, status.HTTP_412_PRECONDITION_FAILED)
response = self.client.post(url, data, format='multipart')

self.assertEqual(status.HTTP_412_PRECONDITION_FAILED, response.status_code)

def test_create_update_textarea(self):

Expand All @@ -73,10 +69,7 @@ def test_create_update_textarea(self):
'value': '<p>this is a test</p>'
}

logging.getLogger('test').info('using access token {token}'.format(token=self.access_token))

response = self.client.post('{url}?access_token={token}'.format(url=url, token=self.access_token), data,
format='multipart')
response = self.client.post(url, data, format='multipart')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
json_response = json.loads(response.content)
self.assertEqual(ConfigValue.objects.filter(id=json_response['id']).count(), 1)
Expand All @@ -91,8 +84,7 @@ def test_create_update_textarea(self):
#'type': 'TEXTAREA',
}

response = self.client.put('{url}?access_token={token}'.format(url=url, token=self.access_token), data,
format='multipart')
response = self.client.put(url, data, format='multipart')
self.assertEqual(response.status_code, status.HTTP_200_OK)
json_response = json.loads(response.content)
self.assertEqual(ConfigValue.objects.filter(id=json_response['id']).count(), 1)
Expand All @@ -111,20 +103,19 @@ def test_create_delete_textarea(self):
'value': '<p>this is a test</p>'
}

logging.getLogger('test').info('using access token {token}'.format(token=self.access_token))
current_qty = ConfigValue.objects.count()

response = self.client.post('{url}?access_token={token}'.format(url=url, token=self.access_token), data,
format='multipart')
response = self.client.post(url, data, format='multipart')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(ConfigValue.objects.count(), 1)
db_object = ConfigValue.objects.get()
self.assertEqual(current_qty + 1, ConfigValue.objects.count())
db_object = ConfigValue.objects.last()
self.assertEqual(db_object.key, 'key.1')

url = reverse('config-values-write:update_destroy', kwargs={'pk': db_object.id})

response = self.client.delete('{url}?access_token={token}'.format(url=url, token=self.access_token))
response = self.client.delete(url)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(ConfigValue.objects.count(), 0)
self.assertEqual(current_qty, ConfigValue.objects.count())

def test_create_update_hexcolor(self):
url = reverse('config-values-write:add')
Expand All @@ -136,10 +127,7 @@ def test_create_update_hexcolor(self):
'value': '#c4c4c4'
}

logging.getLogger('test').info('using access token {token}'.format(token=self.access_token))

response = self.client.post('{url}?access_token={token}'.format(url=url, token=self.access_token), data,
format='multipart')
response = self.client.post(url, data, format='multipart')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
json_response = json.loads(response.content)
self.assertEqual(ConfigValue.objects.filter(id=json_response['id']).count(), 1)
Expand All @@ -152,8 +140,7 @@ def test_create_update_hexcolor(self):
'value': '#050505',
}

response = self.client.put('{url}?access_token={token}'.format(url=url, token=self.access_token), data,
format='multipart')
response = self.client.put(url, data, format='multipart')
self.assertEqual(response.status_code, status.HTTP_200_OK)
json_response = json.loads(response.content)
self.assertEqual(ConfigValue.objects.filter(id=json_response['id']).count(), 1)
Expand All @@ -171,16 +158,12 @@ def test_create_invalid_hexcolor(self):
'value': '#c4c4c4c4c4'
}

logging.getLogger('test').info('using access token {token}'.format(token=self.access_token))

response = self.client.post('{url}?access_token={token}'.format(url=url, token=self.access_token), data,
format='multipart')
response = self.client.post(url, data, format='multipart')
self.assertEqual(response.status_code, status.HTTP_412_PRECONDITION_FAILED)

def test_create_clone(self):
url = reverse('config-values-write:clone', kwargs={'show_id': 1, 'to_show_id': 3})

logging.getLogger('test').info('using access token {token}'.format(token=self.access_token))
response = self.client.post('{url}?access_token={token}'.format(url=url, token=self.access_token), )
response = self.client.post(url)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(ConfigValue.objects.filter(show_id=3).count() > 0, True)
4 changes: 2 additions & 2 deletions api/tests/public_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ def test_get_by_show_id(self):
response = self.client.get('{url}?page=2&per_page=5'.format(url=url))
self.assertEqual(response.status_code, status.HTTP_200_OK)
json_response = json.loads(response.content)
self.assertEqual(json_response['current_page'] == 2)
self.assertEqual(json_response['per_page'] == 5)
self.assertEqual(2, json_response['current_page'])
self.assertEqual(5, json_response['per_page'])
16 changes: 16 additions & 0 deletions api/tests/storage_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.core.files.base import ContentFile
from django.test import TestCase

from api.utils.storage import S3Storage, SwiftStorage


class StorageTests(TestCase):
def test_s3_storage(self):
storage = S3Storage()
name = storage.save("test_file.txt", ContentFile(b"test"))
self.assertTrue(storage.exists(name))

def test_swift_storage(self):
storage = SwiftStorage()
name = storage.save("test_file.txt", ContentFile(b"test"))
self.assertTrue(storage.exists(name))
9 changes: 8 additions & 1 deletion api/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@ def config(name: str, default=None):
:param default: Value if setting is unfound
:returns: Setting's value
"""
return getattr(settings, name, default)
vars = name.split('.')
entry = None
for v in vars:
entry = getattr(settings, v, default) if entry is None else (entry[v] if v in entry else None)

entry = default if entry is None else entry

return entry
5 changes: 4 additions & 1 deletion api/utils/storage/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
from .swift_storage import SwiftStorage
from .swift import SwiftStorage
from .s3 import S3Storage

__all__ = ['SwiftStorage', 'S3Storage']
57 changes: 57 additions & 0 deletions api/utils/storage/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from abc import ABC, abstractmethod
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible


class BaseCloudFile(ABC):

def __init__(self, name, storage):
self.name = name
self._storage = storage
self._file = None

@abstractmethod
def _get_file(self):
pass

def _set_file(self, value):
self._file = value

file = property(_get_file, _set_file)


@deconstructible
class BaseCloudStorage(Storage, ABC):

def __init__(self, **settings):
for name, value in settings.items():
if hasattr(self, name):
setattr(self, name, value)

@abstractmethod
def _get_client(self):
pass

@abstractmethod
def _save(self, name, content, headers=None):
pass

@abstractmethod
def _open(self, name, mode='rb'):
pass

@abstractmethod
def delete(self, name):
pass

@abstractmethod
def exists(self, name):
pass

@abstractmethod
def size(self, name):
pass

@abstractmethod
def url(self, name):
pass
Loading