WIP: 2021 02 24 dockerize (#18)
* Use cache for most fields and admin form for m2m files * MR comments/clean up * Cache should obey exclude and fields * Some more tests and docs * Only use cache for image files * Even more tests and handle save as new * fix test * More tests * minor refactor * Improve test coverage * Adding tests for fieldsets * Added cache timeout * Added another test for an edge case * Fix issue with ManagementForm tampered with * Update cache to only set when form is_multipart * Even more testing changes * Dockerize * Setting up tests in docker * Update github actions * Got first integration test to work * Refactor a bit * Fix github action yml * Use docker-compose up -d in github actions * combine coveralls * Updated readme * Clean up code * Remove dup code from rebase Co-authored-by: Thu Trang Pham <thu@joinmodernhealth.com>main
parent
06d3e1a208
commit
4f50c63f7b
|
|
@ -0,0 +1,36 @@
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
sdist/
|
||||||
|
.eggs/
|
||||||
|
*.egg-info/
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Editor settings
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# pycharm
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
tmp/
|
||||||
|
|
@ -49,8 +49,24 @@ jobs:
|
||||||
parallel: true
|
parallel: true
|
||||||
flag-name: Unit Test
|
flag-name: Unit Test
|
||||||
|
|
||||||
|
integration-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Build Docker
|
||||||
|
run: docker-compose build
|
||||||
|
- name: Start Docker
|
||||||
|
run: docker-compose up -d
|
||||||
|
- name: Integration Test
|
||||||
|
run: docker-compose run web make test-all
|
||||||
|
- name: Coveralls
|
||||||
|
uses: AndreMiras/coveralls-python-action@develop
|
||||||
|
with:
|
||||||
|
parallel: true
|
||||||
|
flag-name: Integration Test
|
||||||
|
|
||||||
coveralls:
|
coveralls:
|
||||||
needs: test
|
needs: [test, integration-test]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Coveralls Finished
|
- name: Coveralls Finished
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
django-admin-confirm-3.8
|
django-admin-confirm-3.8.0
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
FROM python:3
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV USE_DOCKER=true
|
||||||
|
WORKDIR /code
|
||||||
|
COPY requirements.txt /code/
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
COPY . /code/
|
||||||
7
Makefile
7
Makefile
|
|
@ -2,9 +2,16 @@ run:
|
||||||
./tests/manage.py runserver
|
./tests/manage.py runserver
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
coverage run --source admin_confirm --branch -m pytest --ignore=admin_confirm/tests/integration
|
||||||
|
coverage report -m
|
||||||
|
|
||||||
|
test-all:
|
||||||
coverage run --source admin_confirm --branch -m pytest
|
coverage run --source admin_confirm --branch -m pytest
|
||||||
coverage report -m
|
coverage report -m
|
||||||
|
|
||||||
|
t:
|
||||||
|
python -m pytest --last-failed -x
|
||||||
|
|
||||||
check-readme:
|
check-readme:
|
||||||
python -m readme_renderer README.md -o /tmp/README.html
|
python -m readme_renderer README.md -o /tmp/README.html
|
||||||
|
|
||||||
|
|
|
||||||
68
README.md
68
README.md
|
|
@ -5,11 +5,11 @@
|
||||||
|
|
||||||
AdminConfirmMixin is a mixin for ModelAdmin to add confirmations to change, add and actions.
|
AdminConfirmMixin is a mixin for ModelAdmin to add confirmations to change, add and actions.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
It can be configured to add a confirmation page on ModelAdmin upon:
|
It can be configured to add a confirmation page on ModelAdmin upon:
|
||||||
|
|
||||||
|
|
@ -139,18 +139,21 @@ Your appreciation is also very welcome :) Feel free to:
|
||||||
|
|
||||||
### Local Development Setup
|
### Local Development Setup
|
||||||
|
|
||||||
|
**Local:**
|
||||||
|
_You can skip this and just use docker if you want_
|
||||||
|
|
||||||
Install pyenv
|
Install pyenv
|
||||||
Install python 3.8
|
pyenv install 3.8.0
|
||||||
|
|
||||||
Create virtualenv via pyenv
|
Create **virtualenv** via pyenv
|
||||||
|
|
||||||
```
|
```
|
||||||
pyenv vituralenv 3.8 django-admin-confirm-3.8
|
pyenv vituralenv 3.8.0 django-admin-confirm-3.8.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Now your terminal should have `(django-admin-confirm-3.8)` prefix, because `.python-version` should have auto switch your virtual env
|
Now your terminal should have `(django-admin-confirm-3.8.0)` prefix, because `.python-version` should have auto switch your virtual env
|
||||||
|
|
||||||
Run migrations and create a superuser and run the server
|
Run **migrations** and create a superuser and run the server
|
||||||
|
|
||||||
```
|
```
|
||||||
./tests/manage.py migrate
|
./tests/manage.py migrate
|
||||||
|
|
@ -160,19 +163,54 @@ Run migrations and create a superuser and run the server
|
||||||
|
|
||||||
You should be able to see the test app at `localhost:8000/admin`
|
You should be able to see the test app at `localhost:8000/admin`
|
||||||
|
|
||||||
Running tests:
|
**Running tests:**
|
||||||
|
|
||||||
```
|
```sh
|
||||||
make test
|
make test # Runs unit tests with coverage locally without integration tests
|
||||||
|
make test-all # Runs unit tests + integration tests, requires extra setup to run locally
|
||||||
```
|
```
|
||||||
|
|
||||||
Testing new changes on test project:
|
Use `python -m pytest` if you want to pass in arguments
|
||||||
|
|
||||||
|
`make t` is a short cut to run without coverage, last-failed, and fail fast
|
||||||
|
|
||||||
|
Testing local changes on test project:
|
||||||
|
|
||||||
```
|
```
|
||||||
pip install -e .
|
pip install -e .
|
||||||
make run
|
make run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Docker:**
|
||||||
|
|
||||||
|
Instead of local set-up, you can also use docker.
|
||||||
|
|
||||||
|
Install docker-compose (or Docker Desktop which installs this for you)
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose build
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now be able to see the app running on `localhost:8000`
|
||||||
|
|
||||||
|
If you haven't already done migrations and created a superuser, you'll want to do it here
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose exec web tests/manage.py migrate
|
||||||
|
docker-compose exec web tests/manage.py createsuperuser
|
||||||
|
```
|
||||||
|
|
||||||
|
Running tests in docker:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose exec -T web make test-all
|
||||||
|
```
|
||||||
|
|
||||||
|
The integration tests are set up within docker. I recommend running the integration tests only in docker.
|
||||||
|
|
||||||
|
Docker is also set to mirror local folder so that you can edit code/tests and don't have to rebuild to run new code/tests.
|
||||||
|
|
||||||
### Release process
|
### Release process
|
||||||
|
|
||||||
Honestly this part is just for my reference. But who knows :) maybe we'll have another maintainer in the future.
|
Honestly this part is just for my reference. But who knows :) maybe we'll have another maintainer in the future.
|
||||||
|
|
@ -180,7 +218,7 @@ Honestly this part is just for my reference. But who knows :) maybe we'll have a
|
||||||
Run tests, check coverage, check readme
|
Run tests, check coverage, check readme
|
||||||
|
|
||||||
```
|
```
|
||||||
make test
|
docker-compose exec -T web make test-all
|
||||||
make check-readme
|
make check-readme
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -188,7 +226,7 @@ Update version in `setup.py`
|
||||||
|
|
||||||
```
|
```
|
||||||
make package
|
make package
|
||||||
make upload-testpypi
|
make upload-testpypi VERSION=<VERSION>
|
||||||
```
|
```
|
||||||
|
|
||||||
Install new version locally
|
Install new version locally
|
||||||
|
|
@ -196,7 +234,7 @@ First you have to uninstall if you used `pip install -e` earlier
|
||||||
|
|
||||||
```
|
```
|
||||||
pip uninstall django_admin_confirm
|
pip uninstall django_admin_confirm
|
||||||
make install-testpypi
|
make install-testpypi VERSION=<VERSION>
|
||||||
```
|
```
|
||||||
|
|
||||||
Update version in `requirements.txt`
|
Update version in `requirements.txt`
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1">{% endif %}
|
{% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1">{% endif %}
|
||||||
{% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}">{% endif %}
|
{% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}">{% endif %}
|
||||||
{% if form.is_multipart %}<input type="hidden" name=CONFIRMATION_RECEIVED value="True">{% endif %}
|
{% if form.is_multipart %}<input type="hidden" name="_confirmation_received" value="True">{% endif %}
|
||||||
<div class="submit-row">
|
<div class="submit-row">
|
||||||
<input type="submit" value="{% trans 'Yes, I’m sure' %}" name="{{ submit_name }}">
|
<input type="submit" value="{% trans 'Yes, I’m sure' %}" name="{{ submit_name }}">
|
||||||
<p class="deletelink-box">
|
<p class="deletelink-box">
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ from django.core.cache import cache
|
||||||
from django.test import TestCase, RequestFactory
|
from django.test import TestCase, RequestFactory
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
class AdminConfirmTestCase(TestCase):
|
class AdminConfirmTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Helper TestCase class and common associated assertions
|
Helper TestCase class and common associated assertions
|
||||||
|
|
@ -43,7 +42,7 @@ class AdminConfirmTestCase(TestCase):
|
||||||
self.assertNotIn("_confirm_change", rendered_content)
|
self.assertNotIn("_confirm_change", rendered_content)
|
||||||
|
|
||||||
confirmation_received_html = (
|
confirmation_received_html = (
|
||||||
'<input type="hidden" name=CONFIRMATION_RECEIVED value="True">'
|
'<input type="hidden" name="_confirmation_received" value="True">'
|
||||||
)
|
)
|
||||||
|
|
||||||
if multipart_form:
|
if multipart_form:
|
||||||
|
|
@ -56,3 +55,31 @@ class AdminConfirmTestCase(TestCase):
|
||||||
for k, v in fields.items():
|
for k, v in fields.items():
|
||||||
self.assertIn(f'name="{k}"', rendered_content)
|
self.assertIn(f'name="{k}"', rendered_content)
|
||||||
self.assertIn(f'value="{v}"', rendered_content)
|
self.assertIn(f'value="{v}"', rendered_content)
|
||||||
|
|
||||||
|
def _assertFormsetsFormHtml(self, rendered_content, inlines):
|
||||||
|
for inline in inlines:
|
||||||
|
for field in inline.fields:
|
||||||
|
self.assertIn("apple", rendered_content)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import socket
|
||||||
|
from django.test import LiveServerTestCase
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
|
||||||
|
|
||||||
|
class AdminConfirmIntegrationTestCase(LiveServerTestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.host = socket.gethostbyname(socket.gethostname())
|
||||||
|
cls.selenium = webdriver.Remote(
|
||||||
|
command_executor="http://selenium:4444/wd/hub",
|
||||||
|
desired_capabilities=DesiredCapabilities.FIREFOX,
|
||||||
|
)
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
cls.selenium.quit()
|
||||||
|
super().tearDownClass()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
from admin_confirm.tests.helpers import AdminConfirmIntegrationTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class SmokeTest(AdminConfirmIntegrationTestCase):
|
||||||
|
def test_load_admin(self):
|
||||||
|
self.selenium.get(self.live_server_url+'/admin/')
|
||||||
|
self.assertIn('Django', self.selenium.title)
|
||||||
|
|
@ -6,7 +6,7 @@ from tests.market.admin import ShoppingMallAdmin
|
||||||
from tests.market.models import GeneralManager, ShoppingMall, Town
|
from tests.market.models import GeneralManager, ShoppingMall, Town
|
||||||
from tests.factories import ShopFactory
|
from tests.factories import ShopFactory
|
||||||
|
|
||||||
from admin_confirm.constants import CACHE_KEYS, CONFIRMATION_RECEIVED
|
from admin_confirm.constants import CACHE_KEYS
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(ShoppingMallAdmin, "inlines", [])
|
@mock.patch.object(ShoppingMallAdmin, "inlines", [])
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
command: python tests/manage.py runserver 0.0.0.0:8000
|
||||||
|
volumes:
|
||||||
|
- .:/code
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
depends_on:
|
||||||
|
- selenium
|
||||||
|
selenium:
|
||||||
|
# image: selenium/standalone-firefox
|
||||||
|
image: selenium/standalone-firefox-debug:latest
|
||||||
|
ports:
|
||||||
|
- "4444:4444" # Selenium
|
||||||
|
- "5900:5900" # VNC
|
||||||
|
|
@ -17,12 +17,3 @@ class ItemAdmin(AdminConfirmMixin, ModelAdmin):
|
||||||
def image_preview(self, obj):
|
def image_preview(self, obj):
|
||||||
if obj.image:
|
if obj.image:
|
||||||
return mark_safe('<img src="{obj.image.url}" />')
|
return mark_safe('<img src="{obj.image.url}" />')
|
||||||
|
|
||||||
# def one(self, obj):
|
|
||||||
# return "Read Only"
|
|
||||||
|
|
||||||
# def two(self, obj):
|
|
||||||
# return "Read Only"
|
|
||||||
|
|
||||||
# def three(self, obj):
|
|
||||||
# return "Read Only"
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,13 @@ SECRET_KEY = "=yddl-40388w3e2hl$e8)revce=n67_idi8pfejtn3!+2%!_qt"
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
|
USE_DCOKER = os.environ.get("USE_DOCKER", '').lower() == "true"
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
|
||||||
|
if USE_DCOKER:
|
||||||
|
import socket
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = [socket.gethostbyname(socket.gethostname())]
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue