Aggiorno dall'upstream

main
Davide Borgonovo 2024-07-23 17:25:27 +02:00
parent 900978936e
commit 79de619503
28 changed files with 165 additions and 68 deletions

4
.env
View File

@ -1,3 +1,3 @@
PYTHON_VERSION=3.8
DJANGO_VERSION=3.1.7
PYTHON_VERSION=3.9.9
DJANGO_VERSION=4.0.1
SELENIUM_VERSION=4.0.0a7

View File

@ -30,7 +30,7 @@ jobs:
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
# enable persistance
DATA_DIR: /tmp/localstack/data
PERSISTENCE: 1
LAMBDA_EXECUTOR: local
DOCKER_HOST: unix:///var/run/docker.sock
DEBUG: true
@ -38,7 +38,7 @@ jobs:
# It doesn't seem like the scripts in entrypoint are being ran... or they are not copied over since
# the checkout action happens after init services on Github Actions
# - "${{ github.workspace }}/docker-entrypoint-initaws.d:/docker-entrypoint-initaws.d"
- "${{ github.workspace }}/tmp/localstack:/tmp/localstack"
- "${{ github.workspace }}/volume:/var/lib/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
ports:
- 4566:4566
@ -46,8 +46,12 @@ jobs:
options: --health-cmd="curl http://localhost:4566/health?reload" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
django-version: [2.2, 3.0]
python-version: [3.8, 3.9, 3.10.0, 3.11]
django-version: [3.2, 4.0.4]
include:
# Version 4.0 of Django drops support for python 3.6 & 3.7
- python-version: 3.7
django-version: 3.2
env:
DJANGO_VERSION: ${{ matrix.django-version }}
PYTHON_VERSION: ${{ matrix.python-version }}
@ -59,7 +63,7 @@ jobs:
- name: Update Permissions
run: |
sudo chown -R $USER:$USER ${{ github.workspace }}
# required because actions/checkout@2 wants to delete the /tmp/localstack folder
# required because actions/checkout@2 wants to delete the localstack folder
- uses: actions/checkout@v2
- name: Build Docker for Python 3.6
if: ${{ matrix.python-version == 3.6 }}

2
.gitignore vendored
View File

@ -37,3 +37,5 @@ docs/_build/
db.sqlite3
tmp/
.python-version

View File

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

View File

@ -4,6 +4,8 @@ ENV PYTHONUNBUFFERED=1
ENV USE_DOCKER=true
WORKDIR /code
COPY . /code/
RUN echo "Use legency resolver for pip. It does not use the feature back-tracking. Thus is easier to debug imcompatible dependencies."
RUN pip install --upgrade pip --use-deprecated=legacy-resolver
ARG DJANGO_VERSION="3.1.7"
RUN echo "Installing Django Version: ${DJANGO_VERSION}"
RUN pip install django==${DJANGO_VERSION}

View File

@ -16,7 +16,7 @@ test-integration:
coverage run --source admin_confirm --branch -m pytest --ignore=admin_confirm/tests/unit
docker-exec:
docker-compose exec -T web ${COMMAND}
docker-compose -f docker-compose.dev.yml exec -T web ${COMMAND}
check-readme:
python -m readme_renderer README.md -o /tmp/README.html

View File

@ -110,7 +110,7 @@ Note: `confirmation_fields` apply to both add/change confirmations.
**Confirm Action:**
```py
from admin_confirm import AdminConfirmMixin
from admin_confirm import AdminConfirmMixin, confirm_action
class MyModelAdmin(AdminConfirmMixin, ModelAdmin):
actions = ["action1", "action2"]

View File

@ -1,2 +1,3 @@
__all__ = ["admin"]
__all__ = ["admin", "confirm_action"]
from .admin import AdminConfirmMixin # noqa
from .admin import confirm_action # noqa

View File

@ -1,3 +1,4 @@
import functools
from typing import Dict
from django.contrib.admin.exceptions import DisallowedModelAdminToField
from django.contrib.admin.utils import flatten_fieldsets, unquote
@ -5,11 +6,11 @@ from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.template.response import TemplateResponse
from django.contrib.admin.options import TO_FIELD_VAR
from django.utils.decorators import method_decorator
from django.utils.translation import gettext as _
from django.contrib.admin import helpers
from django.db.models import Model, ManyToManyField, FileField, ImageField
from django.forms import ModelForm
from django.utils.decorators import method_decorator
from admin_confirm.utils import (
log,
get_admin_change_url,
@ -433,6 +434,7 @@ class AdminConfirmMixin:
"app_label": opts.app_label,
"model_name": opts.model_name,
"opts": opts,
"obj": obj,
"changed_data": changed_data,
"add": add,
"save_as_new": SAVE_AS_NEW in request.POST,
@ -454,6 +456,7 @@ def confirm_action(func):
return to the changelist without performing action.
"""
@functools.wraps(func)
def func_wrapper(modeladmin, request, queryset):
# First called by `Go` which would not have confirm_action in params
if request.POST.get("_confirm_action"):

View File

@ -106,5 +106,9 @@ class FileCache(object):
def delete_all(self):
"Delete all cached file data from cache."
self.cache.delete_many(self.cached_keys)
self.cached_keys = []
# Issue #46 Redis Cache errs if we call delete_many with an empty list - fixed in Django 4.2
# Note: set_many() should check for empty data in redis too.
# See: https://github.com/django/django/commit/608ab043f75f1f9c094de57d2fd678f522bb8243
if self.cached_keys:
self.cache.delete_many(self.cached_keys)
self.cached_keys = []

View File

@ -9,7 +9,7 @@
</tr>
{% for field, values in changed_data.items %}
<tr>
<td>{{ field }}</td>
<td>{% verbose_name obj field %}</td>
<td>{{ values.0|format_change_data_field_value }}</td>
<td>{{ values.1|format_change_data_field_value }}</td>
</tr>

View File

@ -17,3 +17,9 @@ def format_change_data_field_value(field_value):
return mark_safe(output)
except Exception:
return field_value
@register.simple_tag
def verbose_name(obj, fieldname):
if obj:
return obj._meta.get_field(fieldname).verbose_name

View File

@ -9,7 +9,15 @@ services:
PYTHON_VERSION: "$PYTHON_VERSION"
DJANGO_VERSION: "$DJANGO_VERSION"
SELENIUM_VERSION: "$SELENIUM_VERSION"
command: python tests/manage.py runserver 0.0.0.0:8000
# Note: collectstatic runs from inside the docker container and needs
# to access localstack through the host machine using host.docker.internal
# BUT when we access the django server from our host machine, we need to access
# the stored staticfiles via localhost, so export LOCALSTACK_HOST before and after
command: >
sh -c "export LOCALSTACK_HOST=host.docker.internal &&
python tests/manage.py collectstatic --no-input &&
export LOCALSTACK_HOST=localhost &&
python tests/manage.py runserver 0.0.0.0:8000"
volumes:
- .:/code
ports:
@ -18,9 +26,8 @@ services:
- selenium
- localstack
environment:
- SELENIUM_HOST=selenium
# Used for localstack_client as well as our project
- LOCALSTACK_HOST=host.docker.internal
- SELENIUM_HOST=host.docker.internal
selenium:
image: selenium/standalone-firefox
@ -32,8 +39,6 @@ services:
localstack:
image: localstack/localstack
container_name: localstack_main
network_mode: bridge
ports:
- "4566:4566"
- "4571:4571"
@ -41,11 +46,11 @@ services:
- SERVICES=s3
- DEBUG=true
# enable persistance
- DATA_DIR=/tmp/localstack/data
- PERSISTENCE=1
- LAMBDA_EXECUTOR=docker
- DOCKER_HOST=unix:///var/run/docker.sock
- HOSTNAME_EXTERNAL=localstack
volumes:
- "./docker-entrypoint-initaws.d:/docker-entrypoint-initaws.d"
- "./tmp/localstack:/tmp/localstack"
- "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"

View File

@ -4,15 +4,15 @@
_You can skip this and just use docker if you want_
Install pyenv
pyenv install 3.8.0
pyenv install 3.9.9
Create **virtualenv** via pyenv
```
pyenv vituralenv 3.8.0 django-admin-confirm-3.8.0
pyenv virtualenv 3.9.9 django-admin-confirm-3.9.9
```
Now your terminal should have `(django-admin-confirm-3.8.0)` prefix, because `.python-version` should have auto switch your virtual env
Now your terminal should have `(django-admin-confirm-3.9.9)` prefix, because `.python-version` should have auto switch your virtual env
Install requirements
@ -63,15 +63,22 @@ from admin_confirm.utils import log
log('Message to send to stdout')
```
**Localstack**:
Localstack is used for integration testing and also in the test project.
To check if localstack is running correctly, go to `http://localhost:4566`
To check if the bucket has been set up correctly, go to `http://localhost:4566/mybucket`
To check if the static files have been set up correctly, go to `http://localhost:4566/mybucket/static/admin/css/base.css`
**Docker:**
Instead of local set-up, you can also use docker.
Instead of local set-up, you can also use docker. You may have to delete `.python-version` to do this.
Install docker-compose (or Docker Desktop which installs this for you)
```
docker-compose build
docker-compose up -d
docker-compose -f docker-compose.dev.yml build
docker-compose -f docker-compose.dev.yml up -d
```
You should now be able to see the app running on `localhost:8000`
@ -79,20 +86,22 @@ 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
docker-compose -f docker-compose.dev.yml exec web tests/manage.py migrate
docker-compose -f docker-compose.dev.yml exec web tests/manage.py createsuperuser
```
Running tests in docker:
```
docker-compose exec -T web make test-all
docker-compose -f docker-compose.dev.yml 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.
Use `docker-compose -f docker-compose.dev.yml up -d --force-recreate` if you need to restart the docker containers. For example when updating the docker-compose.yml file, but if you change `Dockerfile` you have to rebuild.
### Release process
Honestly this part is just for my reference. But who knows :) maybe we'll have another maintainer in the future.
@ -100,7 +109,7 @@ Honestly this part is just for my reference. But who knows :) maybe we'll have a
Run tests, check coverage, check readme
```
docker-compose exec -T web make test-all
docker-compose -f docker-compose.dev.yml exec -T web make test-all
make check-readme
```
@ -126,3 +135,5 @@ make run
```
Go on github and make a release in UI
To update supported version badges, use https://shields.io

View File

@ -7,6 +7,7 @@ readme-renderer~=28.0
twine~=3.3.0
coveralls~=3.0.0
Pillow~=8.1.0 # For ImageField
wheel~=0.37.1
### SELENIUM ###
# Known issue: https://github.com/SeleniumHQ/selenium/issues/8762
@ -18,7 +19,7 @@ selenium~=4.0.0.a5
### END SELENIUM ###
### S3 ###
localstack~=0.12.9.1 # For testing with S3
localstack~=1.0.0 # For testing with S3
django-storages~=1.11.1
boto3~=1.17.47
boto3>=1.20.0
### END S3 ###

View File

@ -6,17 +6,26 @@ exclude =
admin_confirm/tests/*
tests/*
ignore =
D107 # Missing docstring in init
D400 # Doc-string: First line should end with a period
C812 # missing trailing comma
I001 # isort found an import in the wrong position
I004 # sisort found an unexpected blank line in imports
Q000 # Remove bad quotes
WPS110 # Seems to require no one word variable names
WPS305 # Found f string
WPS336 # Explicit string concatination
# Missing docstring in init
D107
# Doc-string: First line should end with a period
D400
# missing trailing comma
C812
# isort found an import in the wrong position
I001
# isort found an unexpected blank line in imports
I004
# Remove bad quotes
Q000
# Seems to require no one word variable names
WPS110
# Found f string
WPS305
# Explicit string concatination
WPS336
per-file-ignores =
admin_confirm/tests/*: D102, WPS118, WPS204
admin_confirm/tests/*: D102, WPS118, WPS204
[coverage:run]
relative_files = True
omit = admin_confirm/tests/*

View File

@ -7,7 +7,7 @@ README = open(os.path.join(here, "README.md")).read()
setup(
name="django-admin-confirm",
version="0.2.5",
version="1.0.0",
packages=["admin_confirm"],
description=("Adds confirmation to Django Admin changes, additions and actions"),
long_description_content_type="text/markdown",
@ -17,9 +17,9 @@ setup(
url="https://github.com/trangpham/django-admin-confirm/",
license="Apache 2.0",
install_requires=[
"Django>=2.2",
"Django>=3.2",
],
python_requires=">=3",
python_requires=">=3.7",
project_urls={
"Release Notes": "https://github.com/TrangPham/django-admin-confirm/releases",
},
@ -27,9 +27,8 @@ setup(
# list files in MANIFEST.in
include_package_data=True,
classifiers=[
"Framework :: Django :: 2.2",
"Framework :: Django :: 3.0",
"Programming Language :: Python :: 3.6",
"Framework :: Django :: 3.2",
"Framework :: Django :: 4.0",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",

View File

@ -1,5 +1,5 @@
from django.core.exceptions import ValidationError
from admin_confirm.admin import AdminConfirmMixin
from admin_confirm import AdminConfirmMixin
from django.contrib.admin import ModelAdmin
from django.forms import ModelForm

View File

@ -1,6 +1,10 @@
from django.contrib.admin import ModelAdmin
from admin_confirm.admin import AdminConfirmMixin
class GeneralManagerAdmin(ModelAdmin):
class GeneralManagerAdmin(AdminConfirmMixin, ModelAdmin):
save_as = True
search_fields = ["name"]
confirm_change = True
confirm_add = True
confirmation_fields = ["name", "headshot"]

View File

@ -1,5 +1,5 @@
from django.contrib.admin import ModelAdmin
from admin_confirm.admin import AdminConfirmMixin, confirm_action
from admin_confirm import AdminConfirmMixin, confirm_action
class ShopAdmin(AdminConfirmMixin, ModelAdmin):

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class MarketConfig(AppConfig):
name = "market"

View File

@ -0,0 +1,28 @@
# Generated by Django 3.2.5 on 2021-07-02 00:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('market', '0012_auto_20210326_0240'),
]
operations = [
migrations.AlterField(
model_name='itemsale',
name='total',
field=models.DecimalField(decimal_places=2, max_digits=5),
),
migrations.AlterField(
model_name='shoppingmall',
name='shops',
field=models.ManyToManyField(blank=True, to='market.Shop'),
),
migrations.AlterField(
model_name='transaction',
name='total',
field=models.DecimalField(decimal_places=2, default=0, max_digits=5),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.1.7 on 2022-04-13 01:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('market', '0013_auto_20210702_0041'),
]
operations = [
migrations.AlterField(
model_name='shoppingmall',
name='general_manager',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='market.generalmanager', verbose_name='manager'),
),
]

View File

@ -54,7 +54,7 @@ class ShoppingMall(models.Model):
name = models.CharField(max_length=120)
shops = models.ManyToManyField(Shop, blank=True)
general_manager = models.OneToOneField(
GeneralManager, on_delete=models.CASCADE, null=True, blank=True
GeneralManager, on_delete=models.CASCADE, null=True, blank=True, verbose_name="manager"
)
town = models.ForeignKey(Town, on_delete=models.CASCADE, null=True, blank=True)

View File

@ -2,5 +2,5 @@
# And running the server are different
# (Possibly due to test_project being within a subfolder)
# This defaults settings to local unless
# DJANGO_SETTINGS is specified.
# DJANGO_SETTINGS_MODULE is specified.
from .local import *

View File

@ -138,15 +138,17 @@ if USE_S3:
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY", "test")
AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME", "mybucket")
AWS_DEFAULT_ACL = None
AWS_S3_CUSTOM_DOMAIN = f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com"
# AWS_S3_CUSTOM_DOMAIN = f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com"
AWS_S3_OBJECT_PARAMETERS = {"CacheControl": "max-age=86400"}
# s3 static settings
STATIC_LOCATION = "static"
STATIC_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/{STATIC_LOCATION}/"
STATIC_LOCATION = "staticfiles"
STATIC_URL = f"{AWS_S3_ENDPOINT_URL}/{STATIC_LOCATION}/"
STATIC_ROOT = os.path.join(BASE_DIR, STATIC_LOCATION)
STATICFILES_STORAGE = "tests.storage_backends.StaticStorage"
# s3 public media settings
PUBLIC_MEDIA_LOCATION = "media"
MEDIA_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/{PUBLIC_MEDIA_LOCATION}/"
PUBLIC_MEDIA_LOCATION = "mediafiles"
MEDIA_URL = f"{AWS_S3_ENDPOINT_URL}/{PUBLIC_MEDIA_LOCATION}/"
MEDIA_ROOT = os.path.join(BASE_DIR, PUBLIC_MEDIA_LOCATION)
DEFAULT_FILE_STORAGE = "tests.storage_backends.PublicMediaStorage"
else:
STATIC_URL = "/staticfiles/"
@ -155,3 +157,5 @@ else:
MEDIA_ROOT = os.path.join(BASE_DIR, "mediafiles")
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),)
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

View File

@ -4,4 +4,6 @@ INSTALLED_APPS = INSTALLED_APPS + ["market"]
WSGI_APPLICATION = "test_project.wsgi.application"
ROOT_URLCONF = "test_project.urls"
USE_S3 = "True"
if USE_S3:
STATICFILES_STORAGE = "storage_backends.StaticStorage"
DEFAULT_FILE_STORAGE = "storage_backends.PublicMediaStorage"

View File

@ -3,5 +3,3 @@ from .base import *
INSTALLED_APPS = INSTALLED_APPS + ["tests.market"]
WSGI_APPLICATION = "tests.test_project.wsgi.application"
ROOT_URLCONF = "tests.test_project.urls"
USE_S3 = "True"