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
Thu Trang Pham 2021-03-03 07:42:24 -08:00 committed by GitHub
parent 06d3e1a208
commit 4f50c63f7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 183 additions and 31 deletions

36
.dockerignore 100644
View File

@ -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/

View File

@ -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

View File

@ -1 +1 @@
django-admin-confirm-3.8 django-admin-confirm-3.8.0

7
Dockerfile 100644
View File

@ -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/

View File

@ -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

View File

@ -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.
![Screenshot of Change Confirmation Page](https://raw.githubusercontent.com/TrangPham/django-admin-confirm/main/screenshot.png) ![Screenshot of Change Confirmation Page](https://raw.githubusercontent.com/TrangPham/django-admin-confirm/302e02b1e483fd41e9a6f0b6803b45cd34c866cf/screenshot.png)
![Screenshot of Add Confirmation Page](https://raw.githubusercontent.com/TrangPham/django-admin-confirm/main/screenshot_confirm_add.png) ![Screenshot of Add Confirmation Page](https://raw.githubusercontent.com/TrangPham/django-admin-confirm/302e02b1e483fd41e9a6f0b6803b45cd34c866cf/screenshot_confirm_add.png)
![Screenshot of Action Confirmation Page](https://raw.githubusercontent.com/TrangPham/django-admin-confirm/main/screenshot_confirm_action.png) ![Screenshot of Action Confirmation Page](https://raw.githubusercontent.com/TrangPham/django-admin-confirm/302e02b1e483fd41e9a6f0b6803b45cd34c866cf/screenshot_confirm_action.png)
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`

View File

@ -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, Im sure' %}" name="{{ submit_name }}"> <input type="submit" value="{% trans 'Yes, Im sure' %}" name="{{ submit_name }}">
<p class="deletelink-box"> <p class="deletelink-box">

View File

@ -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()

View File

@ -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)

View File

@ -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", [])

18
docker-compose.yml 100644
View File

@ -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

View File

@ -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"

View File

@ -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