Add integration tests (#20)

* Adding integration tests with inlines

* Adding more tests

* FIx make file

* Setup CI build matrix to work with integration tests

* Try again

* Fix workflow synctax

* Clean up workflow

* Format and some lint stuff

* Try codecov

* yml

* More Testing

* Minor lint things

* Update

* Try again for codecov

* Updates

* Try?

* Ignore quotes

* Exclude test project

* try this?

* checkout required

* Rename

* clean up configs

* Fix

* Allow to fail

* ignores

* FInish the integration tests for cache

* Fix workflow yml

* fix

* Try up again

* TRy again

* Fix

* Fix

* Fix tests

Co-authored-by: Thu Trang Pham <thu@joinmodernhealth.com>
main
Thu Trang Pham 2021-03-05 19:54:01 -08:00 committed by GitHub
parent 4f50c63f7b
commit ad7409b567
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 677 additions and 185 deletions

View File

@ -1,4 +0,0 @@
[run]
relative_files = True
omit = admin_confirm/tests/*
branch = True

2
.env 100644
View File

@ -0,0 +1,2 @@
PYTHON_VERSION=3.8
DJANGO_VERSION=3.1.7

View File

@ -14,47 +14,41 @@ on:
- created - created
jobs: jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: wemake-python-styleguide
uses: wemake-services/wemake-python-styleguide@0.15.2
with:
path: admin_confirm
reporter: 'github-pr-review'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [3.6, 3.7, 3.8, 3.9] python-version: [3.6, 3.7, 3.8, 3.9]
django-version: [2.2, 3.0] django-version: [2.2, 3.0]
env:
DJANGO_VERSION: ${{ matrix.django-version }}
PYTHON_VERSION: ${{ matrix.python-version }}
COMPOSE_INTERACTIVE_NO_CLI: 1
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }} - name: Build Docker for Python 3.6
uses: actions/setup-python@v2 if: ${{ matrix.python-version == 3.6 }}
with:
python-version: ${{ matrix.python-version }}
- name: Install Django ${{ matrix.django-version }}
run: | run: |
pip install django==${{ matrix.django-version }} export SELENIUM_VERSION=3.141.0
- name: Install dependencies docker-compose build
- name: Build Docker for other Python versions
if: ${{ matrix.python-version != 3.6 }}
run: | run: |
python -m pip install --upgrade pip export SELENIUM_VERSION=4.0.0a7
pip install flake8 pytest docker-compose build
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Unit Test
run: |
make test
- name: Coveralls
uses: AndreMiras/coveralls-python-action@develop
with:
parallel: true
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 - name: Start Docker
run: docker-compose up -d run: docker-compose up -d
- name: Integration Test - name: Integration Test
@ -63,10 +57,9 @@ jobs:
uses: AndreMiras/coveralls-python-action@develop uses: AndreMiras/coveralls-python-action@develop
with: with:
parallel: true parallel: true
flag-name: Integration Test
coveralls: coveralls:
needs: [test, integration-test] needs: [test]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Coveralls Finished - name: Coveralls Finished

View File

@ -1,7 +1,12 @@
FROM python:3 ARG PYTHON_VERSION=3.8
FROM python:${PYTHON_VERSION}
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV USE_DOCKER=true ENV USE_DOCKER=true
WORKDIR /code WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/ COPY . /code/
ARG DJANGO_VERSION="3.1.7"
RUN pip install django==${DJANGO_VERSION}
RUN pip install -r requirements.txt
RUN pip install -e .
ARG SELENIUM_VERSION="4.0.0a7"
RUN pip install selenium~=${SELENIUM_VERSION}

View File

@ -153,6 +153,13 @@ pyenv vituralenv 3.8.0 django-admin-confirm-3.8.0
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.8.0)` prefix, because `.python-version` should have auto switch your virtual env
Install requirements
```
pip install -r requirements.txt
pip install -e .
```
Run **migrations** and create a superuser and run the server Run **migrations** and create a superuser and run the server
``` ```
@ -237,7 +244,6 @@ pip uninstall django_admin_confirm
make install-testpypi VERSION=<VERSION> make install-testpypi VERSION=<VERSION>
``` ```
Update version in `requirements.txt`
Add test locally Add test locally
``` ```

View File

@ -11,7 +11,6 @@ from django.forms import ModelForm
from admin_confirm.utils import get_admin_change_url, snake_to_title_case from admin_confirm.utils import get_admin_change_url, snake_to_title_case
from django.core.cache import cache from django.core.cache import cache
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from django.forms.formsets import all_valid
from admin_confirm.constants import ( from admin_confirm.constants import (
CACHE_TIMEOUT, CACHE_TIMEOUT,
CONFIRMATION_RECEIVED, CONFIRMATION_RECEIVED,
@ -139,7 +138,7 @@ class AdminConfirmMixin:
return [initial_value, new_value] return [initial_value, new_value]
if initial_value: if initial_value:
if new_value == False: if new_value is False:
# Clear has been selected # Clear has been selected
return [initial_value.name, None] return [initial_value.name, None]
elif new_value: elif new_value:
@ -249,7 +248,7 @@ class AdminConfirmMixin:
if CONFIRM_CHANGE in modified_post: if CONFIRM_CHANGE in modified_post:
del modified_post[CONFIRM_CHANGE] del modified_post[CONFIRM_CHANGE]
if object_id and not SAVE_AS_NEW in request.POST: if object_id and SAVE_AS_NEW not in request.POST:
# Update the obj with the new uploaded files # Update the obj with the new uploaded files
# then pass rest of changes to Django # then pass rest of changes to Django
obj = self.model.objects.filter(id=object_id).first() obj = self.model.objects.filter(id=object_id).first()
@ -287,6 +286,20 @@ class AdminConfirmMixin:
cache.delete_many(CACHE_KEYS.values()) cache.delete_many(CACHE_KEYS.values())
return super()._changeform_view(request, object_id, form_url, extra_context) return super()._changeform_view(request, object_id, form_url, extra_context)
def _get_cleared_fields(self, request):
"""
Checks for any ImageField or FileField which have been cleared by user.
Because the form that is generated by Django for the model, would not have the
`<field>-clear` inputs in them, they have to be injected into the hidden form
on the confirmation page.
"""
return [
input_name.split("-clear")[0]
for input_name in request.POST.keys()
if input_name.endswith("-clear")
]
def _change_confirmation_view(self, request, object_id, form_url, extra_context): def _change_confirmation_view(self, request, object_id, form_url, extra_context):
# This code is taken from super()._changeform_view # This code is taken from super()._changeform_view
# https://github.com/django/django/blob/master/django/contrib/admin/options.py#L1575-L1592 # https://github.com/django/django/blob/master/django/contrib/admin/options.py#L1575-L1592
@ -348,10 +361,14 @@ class AdminConfirmMixin:
save_action = key save_action = key
break break
cleared_fields = []
if form.is_multipart(): if form.is_multipart():
cache.set(CACHE_KEYS["post"], request.POST, timeout=CACHE_TIMEOUT) cache.set(CACHE_KEYS["post"], request.POST, timeout=CACHE_TIMEOUT)
cache.set(CACHE_KEYS["object"], new_object, timeout=CACHE_TIMEOUT) cache.set(CACHE_KEYS["object"], new_object, timeout=CACHE_TIMEOUT)
# Handle when files are cleared - since the `form` object would not hold that info
cleared_fields = self._get_cleared_fields(request)
title_action = _("adding") if add_or_new else _("changing") title_action = _("adding") if add_or_new else _("changing")
context = { context = {
**self.admin_site.each_context(request), **self.admin_site.each_context(request),
@ -368,6 +385,7 @@ class AdminConfirmMixin:
"save_as_new": SAVE_AS_NEW in request.POST, "save_as_new": SAVE_AS_NEW in request.POST,
"submit_name": save_action, "submit_name": save_action,
"form": form, "form": form,
"cleared_fields": cleared_fields,
"formsets": formsets, "formsets": formsets,
**(extra_context or {}), **(extra_context or {}),
} }

View File

@ -40,8 +40,11 @@
{% include "admin/change_data.html" %} {% include "admin/change_data.html" %}
<form {% if form.is_multipart %}enctype="multipart/form-data"{% endif %} method="post" {% if add %}action="{% url opts|admin_urlname:'add'%}" {% else %}action="{% url opts|admin_urlname:'change' object_id|admin_urlquote %}"{% endif %}>{% csrf_token %} <form {% if form.is_multipart %}enctype="multipart/form-data"{% endif %} method="post" {% if add %}action="{% url opts|admin_urlname:'add'%}" {% else %}action="{% url opts|admin_urlname:'change' object_id|admin_urlquote %}"{% endif %}>{% csrf_token %}
<div class="hidden"> <div class="hidden" id="hidden-form">
{{form.as_p}} {{form.as_p}}
{% for cleared_field in cleared_fields %}
<input type="checkbox" name="{{ cleared_field }}-clear" checked>
{% endfor %}
{% for formset in formsets %} {% for formset in formsets %}
{{ formset.as_p }} {{ formset.as_p }}
{% endfor %} {% endfor %}

View File

@ -1,5 +1,4 @@
from django import template from django import template
from django.db.models.query import QuerySet
from django.utils.html import escape from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -16,5 +15,5 @@ def format_change_data_field_value(field_value):
output += "<li>" + escape(value) + "</li>" output += "<li>" + escape(value) + "</li>"
output += "</ul>" output += "</ul>"
return mark_safe(output) return mark_safe(output)
except: except Exception:
return field_value return field_value

View File

@ -8,6 +8,12 @@ You seem concerned about the stability and reliability of this package. You're p
So if you want to include this package in your production codebase, be aware that AdminConfirmMixin works best with simple unmodified ModelAdmins. So if you want to include this package in your production codebase, be aware that AdminConfirmMixin works best with simple unmodified ModelAdmins.
# Probable Issues
These are some areas which might/probably have issues that are not currently tested. Use at your own risk!
- [ ] Saving file/image changes on inlines when confirming change on parent model
## Save Options ## Save Options
- [x] Save - [x] Save
@ -71,10 +77,10 @@ Confirmation on inline changes is not a current feature of this project.
Confirmation on add/change of ModelAdmin that includes inlines needs to be tested. Use AdminConfirmMixin with ModelAdmin containing inlines at your own risk. Confirmation on add/change of ModelAdmin that includes inlines needs to be tested. Use AdminConfirmMixin with ModelAdmin containing inlines at your own risk.
- [ ] .inlines - [x] .inlines
- [ ] .get_inline_instances() - [x] .get_inline_instances()
- [ ] .get_inlines() (New in Django 3.0) - [x] .get_inlines() (New in Django 3.0)
- [ ] .get_formsets_with_inlines() - [ ] .get_formsets_with_inlines() ???
#### Options for inlines #### Options for inlines

View File

@ -1,6 +1,12 @@
import socket
from django.core.cache import cache 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
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
class AdminConfirmTestCase(TestCase): class AdminConfirmTestCase(TestCase):
""" """
@ -62,16 +68,10 @@ class AdminConfirmTestCase(TestCase):
self.assertIn("apple", rendered_content) 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): class AdminConfirmIntegrationTestCase(LiveServerTestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
cls.host = socket.gethostbyname(socket.gethostname()) cls.host = socket.gethostbyname(socket.gethostname())
cls.selenium = webdriver.Remote( cls.selenium = webdriver.Remote(
command_executor="http://selenium:4444/wd/hub", command_executor="http://selenium:4444/wd/hub",
@ -79,6 +79,25 @@ class AdminConfirmIntegrationTestCase(LiveServerTestCase):
) )
super().setUpClass() super().setUpClass()
def setUp(self):
self.superuser = User.objects.create_superuser(
username="super", email="super@email.org", password="pass"
)
self.client.force_login(self.superuser)
cookie = self.client.cookies["sessionid"]
self.selenium.get(
self.live_server_url + "/admin/"
) # selenium will set cookie domain based on current page domain
self.selenium.add_cookie(
{"name": "sessionid", "value": cookie.value, "secure": False, "path": "/"}
)
return super().setUp()
def tearDown(self):
cache.clear()
return super().tearDown()
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
cls.selenium.quit() cls.selenium.quit()

View File

@ -3,5 +3,6 @@ from admin_confirm.tests.helpers import AdminConfirmIntegrationTestCase
class SmokeTest(AdminConfirmIntegrationTestCase): class SmokeTest(AdminConfirmIntegrationTestCase):
def test_load_admin(self): def test_load_admin(self):
self.selenium.get(self.live_server_url+'/admin/') self.selenium.get(self.live_server_url + "/admin/")
self.assertIn('Django', self.selenium.title) self.assertIn("Django", self.selenium.title)
self.assertIn("Market", self.selenium.page_source)

View File

@ -0,0 +1,213 @@
"""
Tests confirmation of add/change
on ModelAdmin that utilize caches
"""
import os
import pytest
import pkg_resources
from importlib import reload
from tests.factories import ShopFactory
from tests.market.models import GeneralManager, Item, ShoppingMall, Town
from admin_confirm.tests.helpers import AdminConfirmIntegrationTestCase
from tests.market.admin import shoppingmall_admin
from admin_confirm.constants import CONFIRM_CHANGE
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.remote.file_detector import LocalFileDetector
from django.core.files.uploadedfile import SimpleUploadedFile
from tempfile import NamedTemporaryFile
class ConfirmWithInlinesTests(AdminConfirmIntegrationTestCase):
def setUp(self):
self.selenium.file_detector = LocalFileDetector()
super().setUp()
def tearDown(self):
reload(shoppingmall_admin)
super().tearDown()
def test_models_without_files_should_not_have_confirmation_received(self):
mall = ShoppingMall.objects.create(name="mall")
self.selenium.get(
self.live_server_url + f"/admin/market/shoppingmall/{mall.id}/change/"
)
# Should ask for confirmation of change
self.assertIn(CONFIRM_CHANGE, self.selenium.page_source)
# Change name
name = self.selenium.find_element_by_name("name")
name.send_keys("New Name")
self.selenium.find_element_by_name("_continue").click()
# Should have hidden form containing the updated name
self.assertIn("Confirm", self.selenium.page_source)
hidden_form = self.selenium.find_element_by_id("hidden-form")
name = hidden_form.find_element_by_name("name")
self.assertIn("New Name", name.get_attribute("value"))
with self.assertRaises(NoSuchElementException):
self.selenium.find_element_by_name("_confirmation_received")
self.selenium.find_element_by_name("_continue").click()
# Should persist change
mall.refresh_from_db()
self.assertIn("New Name", mall.name)
def test_models_with_files_should_have_confirmation_received(self):
item = Item.objects.create(name="item", price=1)
self.selenium.get(
self.live_server_url + f"/admin/market/item/{item.id}/change/"
)
# Should ask for confirmation of change
self.assertIn(CONFIRM_CHANGE, self.selenium.page_source)
# Change price
price = self.selenium.find_element_by_name("price")
price.send_keys(2)
self.selenium.find_element_by_name("_continue").click()
# Should have hidden form containing the updated price
self.assertIn("Confirm", self.selenium.page_source)
hidden_form = self.selenium.find_element_by_id("hidden-form")
price = hidden_form.find_element_by_name("price")
self.assertEqual("21.00", price.get_attribute("value"))
self.selenium.find_element_by_name("_confirmation_received")
self.selenium.find_element_by_name("_continue").click()
item.refresh_from_db()
def test_should_save_file_additions(self):
selenium_version = pkg_resources.get_distribution("selenium").parsed_version
if selenium_version.major < 4:
pytest.skip(
"Known issue `https://github.com/SeleniumHQ/selenium/issues/8762` with this selenium version."
)
item = Item.objects.create(
name="item", price=1, currency=Item.VALID_CURRENCIES[0][0]
)
self.selenium.get(
self.live_server_url + f"/admin/market/item/{item.id}/change/"
)
self.assertIn(CONFIRM_CHANGE, self.selenium.page_source)
# Make a change to trigger confirmation page
price = self.selenium.find_element_by_name("price")
price.send_keys(2)
# Upload a new file
self.selenium.find_element_by_id("id_file").send_keys(
os.getcwd() + "/screenshot.png"
)
self.selenium.find_element_by_name("_continue").click()
# Should have hidden form containing the updated price
self.assertIn("Confirm", self.selenium.page_source)
hidden_form = self.selenium.find_element_by_id("hidden-form")
price = hidden_form.find_element_by_name("price")
self.assertEqual("21.00", price.get_attribute("value"))
self.selenium.find_element_by_name("_confirmation_received")
self.selenium.find_element_by_name("_continue").click()
item.refresh_from_db()
self.assertEqual(21, int(item.price))
self.assertIn("screenshot.png", item.file.name)
def test_should_save_file_changes(self):
selenium_version = pkg_resources.get_distribution("selenium").parsed_version
if selenium_version.major < 4:
pytest.skip(
"Known issue `https://github.com/SeleniumHQ/selenium/issues/8762` with this selenium version."
)
file = SimpleUploadedFile(
name="old_file.jpg",
content=open("screenshot.png", "rb").read(),
content_type="image/jpeg",
)
item = Item.objects.create(
name="item", price=1, currency=Item.VALID_CURRENCIES[0][0], file=file
)
self.selenium.get(
self.live_server_url + f"/admin/market/item/{item.id}/change/"
)
self.assertIn(CONFIRM_CHANGE, self.selenium.page_source)
# Make a change to trigger confirmation page
price = self.selenium.find_element_by_name("price")
price.send_keys(2)
# Upload a new file
self.selenium.find_element_by_id("id_file").send_keys(
os.getcwd() + "/screenshot.png"
)
self.selenium.find_element_by_name("_continue").click()
# Should have hidden form containing the updated price
self.assertIn("Confirm", self.selenium.page_source)
hidden_form = self.selenium.find_element_by_id("hidden-form")
price = hidden_form.find_element_by_name("price")
self.assertEqual("21.00", price.get_attribute("value"))
self.selenium.find_element_by_name("_confirmation_received")
self.selenium.find_element_by_name("_continue").click()
item.refresh_from_db()
self.assertEqual(21, int(item.price))
self.assertIn("screenshot.png", item.file.name)
def test_should_remove_file_if_clear_selected(self):
file = SimpleUploadedFile(
name="old_file.jpg",
content=open("screenshot.png", "rb").read(),
content_type="image/jpeg",
)
item = Item.objects.create(
name="item", price=1, currency=Item.VALID_CURRENCIES[0][0], file=file
)
self.selenium.get(
self.live_server_url + f"/admin/market/item/{item.id}/change/"
)
self.assertIn(CONFIRM_CHANGE, self.selenium.page_source)
# Make a change to trigger confirmation page
price = self.selenium.find_element_by_name("price")
price.send_keys(2)
# Choose to clear the existing file
self.selenium.find_element_by_id("file-clear_id").click()
self.assertTrue(
self.selenium.find_element_by_xpath(
".//*[@id='file-clear_id']"
).get_attribute("checked")
)
self.selenium.find_element_by_name("_continue").click()
# Should have hidden form containing the updated price
self.assertIn("Confirm", self.selenium.page_source)
hidden_form = self.selenium.find_element_by_id("hidden-form")
price = hidden_form.find_element_by_name("price")
self.assertEqual("21.00", price.get_attribute("value"))
self.selenium.find_element_by_name("_confirmation_received")
self.selenium.find_element_by_name("_continue").click()
item.refresh_from_db()
self.assertEqual(21, int(item.price))
# Should have cleared `file` since clear was selected
self.assertFalse(item.file)

View File

@ -0,0 +1,202 @@
"""
Tests confirmation of add/change
on ModelAdmin that includes inlines
Does not test confirmation of inline changes
"""
import pytest
import pkg_resources
from importlib import reload
from tests.factories import ShopFactory
from tests.market.models import GeneralManager, ShoppingMall, Town
from admin_confirm.tests.helpers import AdminConfirmIntegrationTestCase
from tests.market.admin import shoppingmall_admin
from admin_confirm.constants import CONFIRM_CHANGE
from selenium.webdriver.support.ui import Select
class ConfirmWithInlinesTests(AdminConfirmIntegrationTestCase):
def setUp(self):
self.admin = shoppingmall_admin.ShoppingMallAdmin
self.admin.inlines = [shoppingmall_admin.ShopInline]
super().setUp()
def tearDown(self):
reload(shoppingmall_admin)
super().tearDown()
def test_should_have_hidden_form(self):
mall = ShoppingMall.objects.create(name="mall")
self.selenium.get(
self.live_server_url + f"/admin/market/shoppingmall/{mall.id}/change/"
)
# Should ask for confirmation of change
self.assertIn(CONFIRM_CHANGE, self.selenium.page_source)
# Change name
name = self.selenium.find_element_by_name("name")
name.send_keys("New Name")
self.selenium.find_element_by_name("_continue").click()
# Should have hidden form containing the updated name
self.assertIn("Confirm", self.selenium.page_source)
hidden_form = self.selenium.find_element_by_id("hidden-form")
name = hidden_form.find_element_by_name("name")
self.assertIn("New Name", name.get_attribute("value"))
self.selenium.find_element_by_name("_continue").click()
# Should persist change
mall.refresh_from_db()
self.assertIn("New Name", mall.name)
def test_should_have_hidden_formsets(self):
# Not having formsets would cause a `ManagementForm tampered with` issue
gm = GeneralManager.objects.create(name="gm")
shops = [ShopFactory(name=i) for i in range(3)]
town = Town.objects.create(name="town")
mall = ShoppingMall.objects.create(name="mall", general_manager=gm, town=town)
mall.shops.set(shops)
self.selenium.get(
self.live_server_url + f"/admin/market/shoppingmall/{mall.id}/change/"
)
self.assertIn(CONFIRM_CHANGE, self.selenium.page_source)
# Make a change to trigger confirmation page
name = self.selenium.find_element_by_name("name")
name.send_keys("New Name")
self.selenium.find_element_by_name("_continue").click()
self.assertIn("Confirm", self.selenium.page_source)
hidden_form = self.selenium.find_element_by_id("hidden-form")
hidden_form.find_element_by_name("ShoppingMall_shops-TOTAL_FORMS")
self.selenium.find_element_by_name("_continue").click()
mall.refresh_from_db()
self.assertIn("New Name", mall.name)
def test_should_have_saved_inline_changes(self):
gm = GeneralManager.objects.create(name="gm")
town = Town.objects.create(name="town")
mall = ShoppingMall.objects.create(name="mall", general_manager=gm, town=town)
shops = [ShopFactory(name=i) for i in range(3)]
self.selenium.get(
self.live_server_url + f"/admin/market/shoppingmall/{mall.id}/change/"
)
self.assertIn(CONFIRM_CHANGE, self.selenium.page_source)
# Make a change to trigger confirmation page
name = self.selenium.find_element_by_name("name")
name.send_keys("New Name")
# Change shops via inline form
select_shop = Select(
self.selenium.find_element_by_name("ShoppingMall_shops-0-shop")
)
select_shop.select_by_value(str(shops[2].id))
self.selenium.find_element_by_name("_continue").click()
self.assertIn("Confirm", self.selenium.page_source)
hidden_form = self.selenium.find_element_by_id("hidden-form")
hidden_form.find_element_by_name("ShoppingMall_shops-TOTAL_FORMS")
self.selenium.find_element_by_name("_continue").click()
mall.refresh_from_db()
self.assertIn("New Name", mall.name)
self.assertIn(shops[2], mall.shops.all())
def test_should_respect_get_inlines(self):
# New in Django 3.0
django_version = pkg_resources.get_distribution("Django").parsed_version
if django_version.major < 3:
pytest.skip(
"get_inlines() introducted in Django 3.0, and is not in this version"
)
shoppingmall_admin.ShoppingMallAdmin.inlines = []
shoppingmall_admin.ShoppingMallAdmin.get_inlines = (
lambda self, request, obj=None: [shoppingmall_admin.ShopInline]
)
gm = GeneralManager.objects.create(name="gm")
town = Town.objects.create(name="town")
mall = ShoppingMall.objects.create(name="mall", general_manager=gm, town=town)
shops = [ShopFactory(name=i) for i in range(3)]
self.selenium.get(
self.live_server_url + f"/admin/market/shoppingmall/{mall.id}/change/"
)
self.assertIn(CONFIRM_CHANGE, self.selenium.page_source)
# Make a change to trigger confirmation page
name = self.selenium.find_element_by_name("name")
name.send_keys("New Name")
# Change shops via inline form
select_shop = Select(
self.selenium.find_element_by_name("ShoppingMall_shops-0-shop")
)
select_shop.select_by_value(str(shops[2].id))
self.selenium.find_element_by_name("_continue").click()
self.assertIn("Confirm", self.selenium.page_source)
hidden_form = self.selenium.find_element_by_id("hidden-form")
hidden_form.find_element_by_name("ShoppingMall_shops-TOTAL_FORMS")
self.selenium.find_element_by_name("_continue").click()
mall.refresh_from_db()
self.assertIn("New Name", mall.name)
self.assertIn(shops[2], mall.shops.all())
def test_should_respect_get_inline_instances(self):
shoppingmall_admin.ShoppingMallAdmin.inlines = []
shoppingmall_admin.ShoppingMallAdmin.get_inline_instances = (
lambda self, request, obj=None: shoppingmall_admin.ShopInline(
self.model, self.admin_site
)
)
gm = GeneralManager.objects.create(name="gm")
town = Town.objects.create(name="town")
mall = ShoppingMall.objects.create(name="mall", general_manager=gm, town=town)
shops = [ShopFactory(name=i) for i in range(3)]
self.selenium.get(
self.live_server_url + f"/admin/market/shoppingmall/{mall.id}/change/"
)
self.assertIn(CONFIRM_CHANGE, self.selenium.page_source)
# Make a change to trigger confirmation page
name = self.selenium.find_element_by_name("name")
name.send_keys("New Name")
# Change shops via inline form
select_shop = Select(
self.selenium.find_element_by_name("ShoppingMall_shops-0-shop")
)
select_shop.select_by_value(str(shops[2].id))
self.selenium.find_element_by_name("_continue").click()
self.assertIn("Confirm", self.selenium.page_source)
hidden_form = self.selenium.find_element_by_id("hidden-form")
hidden_form.find_element_by_name("ShoppingMall_shops-TOTAL_FORMS")
self.selenium.find_element_by_name("_continue").click()
mall.refresh_from_db()
self.assertIn("New Name", mall.name)
self.assertIn(shops[2], mall.shops.all())

View File

@ -21,8 +21,6 @@ class TestAdminOptions(AdminConfirmTestCase):
mall.shops.set(shops) mall.shops.set(shops)
# new values # new values
gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2") town2 = Town.objects.create(name="town2")
data = { data = {
@ -92,7 +90,6 @@ class TestAdminOptions(AdminConfirmTestCase):
# new values # new values
gm2 = GeneralManager.objects.create(name="gm2") gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2") town2 = Town.objects.create(name="town2")
data = { data = {
@ -164,7 +161,6 @@ class TestAdminOptions(AdminConfirmTestCase):
# new values # new values
gm2 = GeneralManager.objects.create(name="gm2") gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2") town2 = Town.objects.create(name="town2")
data = { data = {
@ -205,7 +201,6 @@ class TestAdminOptions(AdminConfirmTestCase):
# new values # new values
gm2 = GeneralManager.objects.create(name="gm2") gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2") town2 = Town.objects.create(name="town2")
data = { data = {
@ -246,7 +241,6 @@ class TestAdminOptions(AdminConfirmTestCase):
# new values # new values
gm2 = GeneralManager.objects.create(name="gm2") gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2") town2 = Town.objects.create(name="town2")
data = { data = {

View File

@ -1,5 +1,4 @@
from unittest import mock from unittest import mock
from admin_confirm.admin import AdminConfirmMixin
from django.urls import reverse from django.urls import reverse
from admin_confirm.tests.helpers import AdminConfirmTestCase from admin_confirm.tests.helpers import AdminConfirmTestCase

View File

@ -170,7 +170,7 @@ class TestFileCache(AdminConfirmTestCase):
self.assertNotIn("You may edit it again below.", message) self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist # Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/") self.assertEqual(response.url, "/admin/market/item/")
# Should not have changed existing item # Should not have changed existing item
item.refresh_from_db() item.refresh_from_db()
@ -290,7 +290,7 @@ class TestFileCache(AdminConfirmTestCase):
data[CONFIRMATION_RECEIVED] = True data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user: with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(f"/admin/market/item/add/", data=data) response = self.client.post("/admin/market/item/add/", data=data)
# Should show message to user with correct obj and path # Should show message to user with correct obj and path
message_user.assert_called_once() message_user.assert_called_once()
message = message_user.call_args[0][1] message = message_user.call_args[0][1]
@ -299,7 +299,7 @@ class TestFileCache(AdminConfirmTestCase):
self.assertNotIn("You may edit it again below.", message) self.assertNotIn("You may edit it again below.", message)
# Should not have redirected to changelist # Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/") self.assertEqual(response.url, "/admin/market/item/")
# Should not have changed existing item # Should not have changed existing item
self.item.refresh_from_db() self.item.refresh_from_db()
@ -353,7 +353,7 @@ class TestFileCache(AdminConfirmTestCase):
data[CONFIRMATION_RECEIVED] = True data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user: with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(f"/admin/market/item/add/", data=data) response = self.client.post("/admin/market/item/add/", data=data)
# Should show message to user with correct obj and path # Should show message to user with correct obj and path
message_user.assert_called_once() message_user.assert_called_once()
message = message_user.call_args[0][1] message = message_user.call_args[0][1]
@ -362,7 +362,7 @@ class TestFileCache(AdminConfirmTestCase):
self.assertNotIn("You may edit it again below.", message) self.assertNotIn("You may edit it again below.", message)
# Should not have redirected to changelist # Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/") self.assertEqual(response.url, "/admin/market/item/")
# Should not have changed existing item # Should not have changed existing item
self.item.refresh_from_db() self.item.refresh_from_db()
@ -398,17 +398,6 @@ class TestFileCache(AdminConfirmTestCase):
"_save": True, "_save": True,
} }
# Upload new file
f2 = SimpleUploadedFile(
name="test_file2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Set cache
cache_item = Item(
name=data["name"], price=data["price"], currency=data["currency"], file=f2
)
# Make sure there's no post cached obj # Make sure there's no post cached obj
cache.delete(CACHE_KEYS["object"]) cache.delete(CACHE_KEYS["object"])
cache.set(CACHE_KEYS["post"], data) cache.set(CACHE_KEYS["post"], data)
@ -418,7 +407,7 @@ class TestFileCache(AdminConfirmTestCase):
data[CONFIRMATION_RECEIVED] = True data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user: with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(f"/admin/market/item/add/", data=data) response = self.client.post("/admin/market/item/add/", data=data)
# Should show message to user with correct obj and path # Should show message to user with correct obj and path
message_user.assert_called_once() message_user.assert_called_once()
message = message_user.call_args[0][1] message = message_user.call_args[0][1]
@ -427,7 +416,7 @@ class TestFileCache(AdminConfirmTestCase):
self.assertNotIn("You may edit it again below.", message) self.assertNotIn("You may edit it again below.", message)
# Should not have redirected to changelist # Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/") self.assertEqual(response.url, "/admin/market/item/")
# Should not have changed existing item # Should not have changed existing item
self.item.refresh_from_db() self.item.refresh_from_db()
@ -463,17 +452,6 @@ class TestFileCache(AdminConfirmTestCase):
"_save": True, "_save": True,
} }
# Upload new file
f2 = SimpleUploadedFile(
name="test_file2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Set cache
cache_item = Item(
name=data["name"], price=data["price"], currency=data["currency"], file=f2
)
# Make sure there's no cache # Make sure there's no cache
cache.delete(CACHE_KEYS["object"]) cache.delete(CACHE_KEYS["object"])
cache.delete(CACHE_KEYS["post"]) cache.delete(CACHE_KEYS["post"])
@ -483,7 +461,7 @@ class TestFileCache(AdminConfirmTestCase):
data[CONFIRMATION_RECEIVED] = True data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user: with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(f"/admin/market/item/add/", data=data) response = self.client.post("/admin/market/item/add/", data=data)
# Should show message to user with correct obj and path # Should show message to user with correct obj and path
message_user.assert_called_once() message_user.assert_called_once()
message = message_user.call_args[0][1] message = message_user.call_args[0][1]
@ -492,7 +470,7 @@ class TestFileCache(AdminConfirmTestCase):
self.assertNotIn("You may edit it again below.", message) self.assertNotIn("You may edit it again below.", message)
# Should not have redirected to changelist # Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/") self.assertEqual(response.url, "/admin/market/item/")
# Should not have changed existing item # Should not have changed existing item
self.item.refresh_from_db() self.item.refresh_from_db()
@ -571,7 +549,7 @@ class TestFileCache(AdminConfirmTestCase):
self.assertNotIn("You may edit it again below.", message) self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist # Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/") self.assertEqual(response.url, "/admin/market/item/")
# Should not have changed existing item # Should not have changed existing item
item.refresh_from_db() item.refresh_from_db()
@ -601,12 +579,6 @@ class TestFileCache(AdminConfirmTestCase):
# Load the Change Item Page # Load the Change Item Page
ItemAdmin.save_as_continue = False ItemAdmin.save_as_continue = False
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Request.POST # Request.POST
data = { data = {
"id": item.id, "id": item.id,
@ -619,14 +591,6 @@ class TestFileCache(AdminConfirmTestCase):
"_saveasnew": True, "_saveasnew": True,
} }
# Set cache
cache_item = Item(
name=data["name"],
price=data["price"],
currency=data["currency"],
image=i2,
)
# Ensure no cached obj # Ensure no cached obj
cache.delete(CACHE_KEYS["object"]) cache.delete(CACHE_KEYS["object"])
cache.set(CACHE_KEYS["post"], data) cache.set(CACHE_KEYS["post"], data)
@ -647,7 +611,7 @@ class TestFileCache(AdminConfirmTestCase):
self.assertNotIn("You may edit it again below.", message) self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist # Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/") self.assertEqual(response.url, "/admin/market/item/")
# Should not have changed existing item # Should not have changed existing item
item.refresh_from_db() item.refresh_from_db()
@ -676,12 +640,6 @@ class TestFileCache(AdminConfirmTestCase):
# Load the Change Item Page # Load the Change Item Page
ItemAdmin.save_as_continue = False ItemAdmin.save_as_continue = False
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Request.POST # Request.POST
data = { data = {
"id": item.id, "id": item.id,
@ -694,14 +652,6 @@ class TestFileCache(AdminConfirmTestCase):
"_saveasnew": True, "_saveasnew": True,
} }
# Set cache
cache_item = Item(
name=data["name"],
price=data["price"],
currency=data["currency"],
image=i2,
)
# Ensure no cache # Ensure no cache
cache.delete(CACHE_KEYS["object"]) cache.delete(CACHE_KEYS["object"])
cache.delete(CACHE_KEYS["post"]) cache.delete(CACHE_KEYS["post"])
@ -722,7 +672,7 @@ class TestFileCache(AdminConfirmTestCase):
self.assertNotIn("You may edit it again below.", message) self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist # Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/") self.assertEqual(response.url, "/admin/market/item/")
# Should not have changed existing item # Should not have changed existing item
item.refresh_from_db() item.refresh_from_db()
@ -790,7 +740,7 @@ class TestFileCache(AdminConfirmTestCase):
self.assertNotIn("You may edit it again below.", message) self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist # Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/") self.assertEqual(response.url, "/admin/market/item/")
# Should have changed existing item # Should have changed existing item
self.assertEqual(Item.objects.count(), 1) self.assertEqual(Item.objects.count(), 1)
@ -890,12 +840,6 @@ class TestFileCache(AdminConfirmTestCase):
# Load the Change Item Page # Load the Change Item Page
ItemAdmin.save_as_continue = False ItemAdmin.save_as_continue = False
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Request.POST # Request.POST
data = { data = {
"id": item.id, "id": item.id,
@ -930,7 +874,7 @@ class TestFileCache(AdminConfirmTestCase):
self.assertNotIn("You may edit it again below.", message) self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist # Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/") self.assertEqual(response.url, "/admin/market/item/")
# Should have changed existing item # Should have changed existing item
self.assertEqual(Item.objects.count(), 1) self.assertEqual(Item.objects.count(), 1)

View File

@ -2,7 +2,13 @@ version: "3.9"
services: services:
web: web:
build: . build:
context: .
dockerfile: Dockerfile
args:
PYTHON_VERSION: "$PYTHON_VERSION"
DJANGO_VERSION: "$DJANGO_VERSION"
SELENIUM_VERSION: "$SELENIUM_VERSION"
command: python tests/manage.py runserver 0.0.0.0:8000 command: python tests/manage.py runserver 0.0.0.0:8000
volumes: volumes:
- .:/code - .:/code
@ -16,3 +22,5 @@ services:
ports: ports:
- "4444:4444" # Selenium - "4444:4444" # Selenium
- "5900:5900" # VNC - "5900:5900" # VNC
volumes:
- .:/code

View File

@ -1,4 +0,0 @@
[pytest]
DJANGO_SETTINGS_MODULE=tests.test_project.settings.test
addopts = --doctest-modules -ra -l --tb=short --show-capture=stdout --color=yes
testpaths = admin_confirm

View File

@ -1,6 +1,5 @@
Django>=1.7.0 Django>=1.7.0
factory-boy~=3.0.1 factory-boy~=3.0.1
django-admin-confirm~=0.2.2
coverage~=5.4 coverage~=5.4
pytest~=6.2.2 pytest~=6.2.2
pytest-django~=4.1.0 pytest-django~=4.1.0
@ -8,4 +7,10 @@ readme-renderer~=28.0
twine~=3.3.0 twine~=3.3.0
coveralls~=3.0.0 coveralls~=3.0.0
Pillow~=8.1.0 # For ImageField Pillow~=8.1.0 # For ImageField
selenium~=3.141.0
# Known issue: https://github.com/SeleniumHQ/selenium/issues/8762
# Python 3.6 should use because selenium 4 doesn't work with py3.6
# selenium~=3.141.0
# Others should use
selenium~=4.0.0.a5

26
setup.cfg 100644
View File

@ -0,0 +1,26 @@
[flake8]
max-complexity = 10
max-line-length = 127
paths = admin_confirm
exclude =
admin_confirm/tests/*
tests/*
ignore =
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
per-file-ignores =
admin_confirm/tests/*: D102, WPS118, WPS204
[coverage:run]
relative_files = True
omit = admin_confirm/tests/*
branch = True
[tool:pytest]
DJANGO_SETTINGS_MODULE=tests.test_project.settings.test
addopts = --doctest-modules -ra -l --tb=short --show-capture=stdout --color=yes
testpaths = admin_confirm

View File

@ -1,4 +1,5 @@
import os import os
from setuptools import setup from setuptools import setup
here = os.path.abspath(os.path.dirname(__file__)) here = os.path.abspath(os.path.dirname(__file__))
@ -8,7 +9,7 @@ setup(
name="django-admin-confirm", name="django-admin-confirm",
version="0.2.3.dev9", version="0.2.3.dev9",
packages=["admin_confirm"], packages=["admin_confirm"],
description="Adds confirmation to Django Admin changes, additions and actions", description=("Adds confirmation to Django Admin changes, additions and actions"),
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
long_description=README, long_description=README,
author="Thu Trang Pham", author="Thu Trang Pham",

View File

@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('market', '0003_auto_20201108_1717'), ("market", "0003_auto_20201108_1717"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='inventory', model_name="inventory",
name='notes', name="notes",
field=models.TextField(blank=True, default='This is the default', null=True), field=models.TextField(
blank=True, default="This is the default", null=True
),
), ),
] ]

View File

@ -6,16 +6,24 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('market', '0004_inventory_notes'), ("market", "0004_inventory_notes"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='ShoppingMall', name="ShoppingMall",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=120)), "id",
('shops', models.ManyToManyField(to='market.Shop')), models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=120)),
("shops", models.ManyToManyField(to="market.Shop")),
], ],
), ),
] ]

View File

@ -7,42 +7,68 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('market', '0005_shoppingmall'), ("market", "0005_shoppingmall"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='GeneralManager', name="GeneralManager",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=120)), "id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=120)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Town', name="Town",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=120)), "id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=120)),
], ],
), ),
migrations.AddField( migrations.AddField(
model_name='item', model_name="item",
name='file', name="file",
field=models.FileField(blank=True, null=True, upload_to='tmp/files'), field=models.FileField(blank=True, null=True, upload_to="tmp/files"),
), ),
migrations.AddField( migrations.AddField(
model_name='item', model_name="item",
name='image', name="image",
field=models.ImageField(blank=True, null=True, upload_to='tmp/items'), field=models.ImageField(blank=True, null=True, upload_to="tmp/items"),
), ),
migrations.AddField( migrations.AddField(
model_name='shoppingmall', model_name="shoppingmall",
name='general_manager', name="general_manager",
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='market.generalmanager'), field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="market.generalmanager",
),
), ),
migrations.AddField( migrations.AddField(
model_name='shoppingmall', model_name="shoppingmall",
name='town', name="town",
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='market.town'), field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="market.town",
),
), ),
] ]

View File

@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('market', '0006_auto_20210222_0312'), ("market", "0006_auto_20210222_0312"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='generalmanager', model_name="generalmanager",
name='headshot', name="headshot",
field=models.ImageField(blank=True, null=True, upload_to='tmp/gm/headshots'), field=models.ImageField(
blank=True, null=True, upload_to="tmp/gm/headshots"
),
), ),
] ]

View File

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('market', '0007_generalmanager_headshot'), ("market", "0007_generalmanager_headshot"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='item', model_name="item",
name='description', name="description",
field=models.TextField(blank=True, null=True), field=models.TextField(blank=True, null=True),
), ),
] ]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.7 on 2021-03-04 03:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("market", "0008_item_description"),
]
operations = [
migrations.AlterField(
model_name="shoppingmall",
name="shops",
field=models.ManyToManyField(blank=True, null=True, to="market.Shop"),
),
]

View File

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

View File

@ -24,13 +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
USE_DCOKER = os.environ.get("USE_DOCKER", '').lower() == "true" USE_DCOKER = os.environ.get("USE_DOCKER", "").lower() == "true"
ALLOWED_HOSTS = ["127.0.0.1", "localhost"] ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
if USE_DCOKER: if USE_DCOKER:
import socket import socket
ALLOWED_HOSTS = [socket.gethostbyname(socket.gethostname())] ALLOWED_HOSTS += [socket.gethostbyname(socket.gethostname())]
# Application definition # Application definition

View File

@ -1,5 +1,5 @@
from .base import * from .base import *
INSTALLED_APPS = INSTALLED_APPS + ['market'] INSTALLED_APPS = INSTALLED_APPS + ["market"]
WSGI_APPLICATION = "test_project.wsgi.application" WSGI_APPLICATION = "test_project.wsgi.application"
ROOT_URLCONF = "test_project.urls" ROOT_URLCONF = "test_project.urls"

View File

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