update for devops

This commit is contained in:
2019-02-15 12:13:43 +00:00
parent fdc17846a7
commit 2571f4f58d
6 changed files with 368 additions and 9 deletions

13
Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
FROM python:3-alpine
ENV PYTHONUNBUFFERED 1
RUN apk update
RUN apk add --no-cache tini bash unrar dcron postgresql-dev
RUN mkdir /src
WORKDIR /src
ADD requirements.txt /src/
RUN apk add --no-cache --virtual .build-deps mariadb-dev build-base \
&& pipenv install --system
&& apk add --virtual .runtime-deps mariadb-connector-c-dev mariadb-connector-c \
&& apk del .build-deps
ADD . /src/
RUN cat /src/cbreader/crontab >> /etc/crontabs/root

16
Pipfile Normal file
View File

@@ -0,0 +1,16 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
ujson = "*"
django-silk = "*"
django-recaptcha2 = "*"
Django = "*"
gunicorn = "*"
[requires]
python_version = "3.7"

189
Pipfile.lock generated Normal file
View File

@@ -0,0 +1,189 @@
{
"_meta": {
"hash": {
"sha256": "484cc2c0943d2a720e2e0d2c4a722378da9e7a4554695c84e6e67a50f4fb8914"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"autopep8": {
"hashes": [
"sha256:33d2b5325b7e1afb4240814fe982eea3a92ebea712869bfd08b3c0393404248c"
],
"version": "==1.4.3"
},
"certifi": {
"hashes": [
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
],
"version": "==2018.11.29"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"django": {
"hashes": [
"sha256:275bec66fd2588dd517ada59b8bfb23d4a9abc5a362349139ddda3c7ff6f5ade",
"sha256:939652e9d34d7d53d74d5d8ef82a19e5f8bb2de75618f7e5360691b6e9667963"
],
"index": "pypi",
"version": "==2.1.7"
},
"django-recaptcha2": {
"hashes": [
"sha256:9ea90db0cec502741be1066c09ec1b8e02a73162a319a042e78e67c4605087af",
"sha256:c0b43851b05c6bf6ebb5ecc890c13ccedacd9bb33d64b4291c74dd6fcbc89366"
],
"index": "pypi",
"version": "==1.4.1"
},
"django-silk": {
"hashes": [
"sha256:ab6b7151a54eaa14d4fc77a58fd75e7c0c8bd60d29c87e55575845a304b0c0eb",
"sha256:bce0e35d2a6ec3688a0c062c6964695beef4a452be48085f2c1e25f685652d9d"
],
"index": "pypi",
"version": "==3.0.1"
},
"gprof2dot": {
"hashes": [
"sha256:48c1e168c28b8a8eb23bf30fda78fe2ef218269a41505341ec27c27083e47cf4"
],
"version": "==2016.10.13"
},
"gunicorn": {
"hashes": [
"sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471",
"sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3"
],
"index": "pypi",
"version": "==19.9.0"
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"markupsafe": {
"hashes": [
"sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
"sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
"sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
"sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
"sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
"sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
"sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
"sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
"sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
"sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
"sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
"sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
"sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
"sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
"sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
"sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
"sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
"sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
"sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
"sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
"sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
"sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
"sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
"sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
"sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
"sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
"sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
"sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
],
"version": "==1.1.0"
},
"pycodestyle": {
"hashes": [
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
],
"version": "==2.5.0"
},
"pygments": {
"hashes": [
"sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a",
"sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d"
],
"version": "==2.3.1"
},
"python-dateutil": {
"hashes": [
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
],
"version": "==2.8.0"
},
"pytz": {
"hashes": [
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
"sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
],
"version": "==2018.9"
},
"requests": {
"hashes": [
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
],
"version": "==2.21.0"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
],
"version": "==1.12.0"
},
"sqlparse": {
"hashes": [
"sha256:ce028444cfab83be538752a2ffdb56bc417b7784ff35bb9a3062413717807dec",
"sha256:d9cf190f51cbb26da0412247dfe4fb5f4098edb73db84e02f9fc21fdca31fed4"
],
"version": "==0.2.4"
},
"ujson": {
"hashes": [
"sha256:f66073e5506e91d204ab0c614a148d5aa938bdbf104751be66f8ad7a222f5f86"
],
"index": "pypi",
"version": "==1.35"
},
"urllib3": {
"hashes": [
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
],
"version": "==1.24.1"
}
},
"develop": {}
}

View File

@@ -78,6 +78,10 @@ class ComicBook(models.Model):
def __str__(self):
return self.file_name
@property
def selector_string(self):
return urlsafe_base64_encode(self.selector.bytes).decode()
def get_image(self, page):
base_dir = Setting.objects.get(name='BASE_DIR').value
if self.directory:
@@ -329,7 +333,11 @@ class ComicStatus(models.Model):
return self.last_read_page
def __str__(self):
return 'C:{0} P:{1}'.format(self.comic.file_name, self.last_read_page)
return self.__repr__()
def __repr__(self):
return f'<ComicStatus:{self.user.username}:{self.comic.file_name}:{self.last_read_page}:' \
f'{self.unread}:{self.finished}'
# TODO: add support to reference items last being read

View File

@@ -1,8 +1,9 @@
import json
import os
from os import path
from django.contrib.auth.models import User
from django.test import TestCase
from django.test import TestCase, Client
from django.utils.http import urlsafe_base64_encode
from comic.models import ComicBook, ComicPage, Setting, ComicStatus, Directory
@@ -14,9 +15,9 @@ from comic.util import generate_directory
class ComicBookTests(TestCase):
def setUp(self):
Setting(name='BASE_DIR', value=path.join(os.getcwd(), 'comic', 'test')).save()
user = User(username='test')
user.save()
Setting.objects.create(name='BASE_DIR', value=path.join(os.getcwd(), 'comic', 'test'))
User.objects.create_user('test', 'test@test.com', 'test')
user = User.objects.first()
ComicBook.process_comic_book('test1.rar')
book = ComicBook.process_comic_book('test2.rar')
status = ComicStatus(user=user,
@@ -191,3 +192,139 @@ class ComicBookTests(TestCase):
def test_page_name(self):
book = ComicBook.objects.get(file_name='test1.rar')
self.assertEqual(book.page_name(0), 'img1.jpg')
def test_comic_list(self):
c = Client()
response = c.get('/comic/')
self.assertEqual(response.status_code, 302)
c.login(username='test', password='test')
response = c.get('/comic/')
self.assertEqual(response.status_code, 200)
user = User.objects.first()
generate_directory(user)
directory = Directory.objects.first()
response = c.get(f'/comic/{urlsafe_base64_encode(directory.selector.bytes).decode()}/')
self.assertEqual(response.status_code, 200)
def test_comic_list_json(self):
c = Client()
response = c.post('/comic/list_json/')
self.assertEqual(response.status_code, 302)
c.login(username='test', password='test')
response = c.post('/comic/list_json/')
self.assertEqual(response.status_code, 200)
directory = Directory.objects.first()
response = c.post(f'/comic/list_json/{urlsafe_base64_encode(directory.selector.bytes).decode()}/')
self.assertEqual(response.status_code, 200)
def test_recent_comics(self):
c = Client()
response = c.get('/comic/recent/')
self.assertEqual(response.status_code, 302)
c.login(username='test', password='test')
response = c.get('/comic/recent/')
self.assertEqual(response.status_code, 200)
def test_recent_comics_json(self):
c = Client()
response = c.post('/comic/recent/json/')
self.assertEqual(response.status_code, 302)
c.login(username='test', password='test')
generate_directory(User.objects.first())
ComicStatus.objects.all().delete()
req_data = {
'start': '0',
'length': '10',
'search[value]': '',
'order[0][dir]': 'desc'
}
response = c.post('/comic/recent/json/', req_data)
self.assertEqual(response.status_code, 200)
req_data['search[value]'] = 'test1.rar'
response = c.post('/comic/recent/json/', req_data)
self.assertEqual(response.status_code, 200)
self.maxDiff = None
book = ComicBook.objects.get(file_name='test1.rar')
self.assertDictEqual(json.loads(response.content),
{'data': [{'date': book.date_added.strftime('%d/%m/%y-%H:%M'),
'icon': '<span class="glyphicon glyphicon-book"></span>',
'label': '<center><span class="label '
'label-default">Unread</span></center>',
'name': 'test1.rar',
'selector': urlsafe_base64_encode(book.selector.bytes).decode(),
'type': 'book',
'url': f'/comic/read/'
f'{urlsafe_base64_encode(book.selector.bytes).decode()}/0/'}],
'recordsFiltered': 1,
'recordsTotal': 4})
req_data['search[value]'] = ''
req_data['order[0][dir]'] = 3
response = c.post('/comic/recent/json/', req_data)
self.assertListEqual([x['name'] for x in json.loads(response.content)['data']],
['test1.rar', 'test2.rar', 'test4.rar', 'test3.rar'])
req_data['order[0][dir]'] = 2
response = c.post('/comic/recent/json/', req_data)
self.assertListEqual([x['name'] for x in json.loads(response.content)['data']],
['test1.rar', 'test2.rar', 'test4.rar', 'test3.rar'])
def test_comic_edit(self):
c = Client()
book: ComicBook = ComicBook.objects.first()
user = User.objects.get(username='test')
response = c.get('/comic/edit/')
self.assertEqual(response.status_code, 302)
c.login(username='test', password='test')
response = c.get('/comic/edit/')
self.assertEqual(response.status_code, 405)
req_data = {"comic_list_length": 10, "func": "unread", "selected": book.selector_string}
response = c.post('/comic/edit/', req_data)
self.assertEqual(response.status_code, 200)
status = ComicStatus.objects.get(comic=book, user=user)
self.assertEqual(status.last_read_page, 0)
self.assertTrue(status.unread)
self.assertFalse(status.finished)
req_data['func'] = 'read'
response = c.post('/comic/edit/', req_data)
self.assertEqual(response.status_code, 200)
status.refresh_from_db()
self.assertEqual(status.last_read_page, 3)
self.assertFalse(status.unread)
self.assertTrue(status.finished)
req_data['func'] = 'choose'
response = c.post('/comic/edit/', req_data)
self.assertEqual(response.status_code, 200)
status.refresh_from_db()
self.assertEqual(status.last_read_page, 3)
self.assertFalse(status.unread)
self.assertTrue(status.finished)
del req_data['selected']
response = c.post('/comic/edit/', req_data)
self.assertEqual(response.status_code, 200)
def test_account_page(self):
c = Client()
user = User.objects.get(username='test')
response = c.get('/comic/account/')
self.assertEqual(response.status_code, 302)
c.login(username='test', password='test')
response = c.get('/comic/account/')
self.assertEqual(response.status_code, 200)
response = c.post('/comic/account/')

View File

@@ -1,4 +0,0 @@
ujson
django
django-silk
django-recaptcha2