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): def __str__(self):
return self.file_name return self.file_name
@property
def selector_string(self):
return urlsafe_base64_encode(self.selector.bytes).decode()
def get_image(self, page): def get_image(self, page):
base_dir = Setting.objects.get(name='BASE_DIR').value base_dir = Setting.objects.get(name='BASE_DIR').value
if self.directory: if self.directory:
@@ -329,7 +333,11 @@ class ComicStatus(models.Model):
return self.last_read_page return self.last_read_page
def __str__(self): 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 # TODO: add support to reference items last being read

View File

@@ -1,8 +1,9 @@
import json
import os import os
from os import path from os import path
from django.contrib.auth.models import User 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 django.utils.http import urlsafe_base64_encode
from comic.models import ComicBook, ComicPage, Setting, ComicStatus, Directory from comic.models import ComicBook, ComicPage, Setting, ComicStatus, Directory
@@ -14,9 +15,9 @@ from comic.util import generate_directory
class ComicBookTests(TestCase): class ComicBookTests(TestCase):
def setUp(self): def setUp(self):
Setting(name='BASE_DIR', value=path.join(os.getcwd(), 'comic', 'test')).save() Setting.objects.create(name='BASE_DIR', value=path.join(os.getcwd(), 'comic', 'test'))
user = User(username='test') User.objects.create_user('test', 'test@test.com', 'test')
user.save() user = User.objects.first()
ComicBook.process_comic_book('test1.rar') ComicBook.process_comic_book('test1.rar')
book = ComicBook.process_comic_book('test2.rar') book = ComicBook.process_comic_book('test2.rar')
status = ComicStatus(user=user, status = ComicStatus(user=user,
@@ -191,3 +192,139 @@ class ComicBookTests(TestCase):
def test_page_name(self): def test_page_name(self):
book = ComicBook.objects.get(file_name='test1.rar') book = ComicBook.objects.get(file_name='test1.rar')
self.assertEqual(book.page_name(0), 'img1.jpg') 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