mirror of
https://github.com/almet/copanier.git
synced 2025-04-28 11:32:38 +02:00
Renamed + remote
This commit is contained in:
parent
a009948e08
commit
3216aeac11
53 changed files with 353 additions and 24 deletions
|
@ -52,7 +52,7 @@ class Roll(Roll):
|
||||||
|
|
||||||
|
|
||||||
env = Environment(
|
env = Environment(
|
||||||
loader=PackageLoader("kaba", "templates"), autoescape=select_autoescape(["kaba"])
|
loader=PackageLoader("copanier", "templates"), autoescape=select_autoescape(["copanier"])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,6 +116,7 @@ async def attach_request(request, response):
|
||||||
@app.listen("startup")
|
@app.listen("startup")
|
||||||
async def on_startup():
|
async def on_startup():
|
||||||
configure()
|
configure()
|
||||||
|
Delivery.init_fs()
|
||||||
|
|
||||||
|
|
||||||
@app.route("/sésame", methods=["GET"])
|
@app.route("/sésame", methods=["GET"])
|
||||||
|
@ -340,7 +341,7 @@ def cli_wrapper():
|
||||||
def serve(reload=False):
|
def serve(reload=False):
|
||||||
"""Run a web server (for development only)."""
|
"""Run a web server (for development only)."""
|
||||||
if reload:
|
if reload:
|
||||||
hupper.start_reloader("kaba.serve")
|
hupper.start_reloader("copanier.serve")
|
||||||
traceback(app)
|
traceback(app)
|
||||||
static(app, root=Path(__file__).parent / "static")
|
static(app, root=Path(__file__).parent / "static")
|
||||||
simple_server(app, port=2244)
|
simple_server(app, port=2244)
|
|
@ -16,7 +16,7 @@ FROM_EMAIL = "contact@epinamap.org"
|
||||||
def init():
|
def init():
|
||||||
for key, value in globals().items():
|
for key, value in globals().items():
|
||||||
if key.isupper():
|
if key.isupper():
|
||||||
env_key = "KABA_" + key
|
env_key = "COPANIER_" + key
|
||||||
typ = type(value)
|
typ = type(value)
|
||||||
if env_key in os.environ:
|
if env_key in os.environ:
|
||||||
globals()[key] = typ(os.environ[env_key])
|
globals()[key] = typ(os.environ[env_key])
|
|
@ -139,6 +139,10 @@ class Delivery(Base):
|
||||||
def is_open(self):
|
def is_open(self):
|
||||||
return datetime.now().date() <= self.order_before.date()
|
return datetime.now().date() <= self.order_before.date()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def init_fs(cls):
|
||||||
|
cls.get_root().mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_root(cls):
|
def get_root(cls):
|
||||||
return Path(config.DATA_ROOT) / cls.__root__
|
return Path(config.DATA_ROOT) / cls.__root__
|
Before Width: | Height: | Size: 758 KiB After Width: | Height: | Size: 758 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
@ -17,7 +17,7 @@
|
||||||
<section class="notification {{ message[1] }}"><i class="icon-megaphone"></i> {{ message[0] }}</section>
|
<section class="notification {{ message[1] }}"><i class="icon-megaphone"></i> {{ message[0] }}</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<section class="menu">
|
<section class="menu">
|
||||||
<h1><a href="/">Panio</a> <small>Les paniers piano d'Épinamap</small></h1>
|
<h1><a href="/">Copanier</a> <small>Les paniers piano d'Épinamap</small></h1>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/livraison">Nouvelle livraison</a>
|
<a href="/livraison">Nouvelle livraison</a>
|
||||||
{% if request["user"] %}
|
{% if request["user"] %}
|
199
remote/__main__.py
Normal file
199
remote/__main__.py
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
import minicli
|
||||||
|
from usine import chown, config, connect, env, exists, mkdir, put, run, sudo, template
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def pip(*command):
|
||||||
|
"""Run a pip command on the remote server.
|
||||||
|
"""
|
||||||
|
with sudo(user="copanier"):
|
||||||
|
run(f"/srv/copanier/venv/bin/pip {' '.join(command)}")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def system():
|
||||||
|
"""Setup the system."""
|
||||||
|
with sudo():
|
||||||
|
run("apt update")
|
||||||
|
run(
|
||||||
|
"apt install -y nginx git software-properties-common gcc "
|
||||||
|
"python3.7 python3.7-dev python3.7-venv pkg-config"
|
||||||
|
)
|
||||||
|
mkdir("/srv/copanier/logs")
|
||||||
|
run("useradd -N copanier -d /srv/copanier/ || exit 0")
|
||||||
|
chown("copanier:users", "/srv/copanier/")
|
||||||
|
run("chsh -s /bin/bash copanier")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def venv():
|
||||||
|
"""Setup the python virtualenv."""
|
||||||
|
path = "/srv/copanier/venv/"
|
||||||
|
if not exists(path):
|
||||||
|
with sudo(user="copanier"):
|
||||||
|
run(f"python3.7 -m venv {path}")
|
||||||
|
pip("install pip -U")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def http():
|
||||||
|
"""Configure Nginx and letsencrypt."""
|
||||||
|
# When we'll have a domain.
|
||||||
|
put("remote/nginx-snippet.conf", "/etc/nginx/snippets/copanier.conf")
|
||||||
|
put("remote/letsencrypt.conf", "/etc/nginx/snippets/letsencrypt.conf")
|
||||||
|
put("remote/ssl.conf", "/etc/nginx/snippets/ssl.conf")
|
||||||
|
domain = config.domains[0]
|
||||||
|
pempath = f"/etc/letsencrypt/live/{domain}/fullchain.pem"
|
||||||
|
with sudo():
|
||||||
|
if exists(pempath):
|
||||||
|
print(f"{pempath} found, using https configuration")
|
||||||
|
conf = template(
|
||||||
|
"remote/nginx-https.conf",
|
||||||
|
domains=" ".join(config.domains),
|
||||||
|
domain=domain,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f"{pempath} not found, using http configuration")
|
||||||
|
# Before letsencrypt.
|
||||||
|
conf = template(
|
||||||
|
"remote/nginx-http.conf",
|
||||||
|
domains=" ".join(config.domains),
|
||||||
|
domain=domain,
|
||||||
|
)
|
||||||
|
put(conf, "/etc/nginx/sites-enabled/copanier.conf")
|
||||||
|
restart("nginx")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def letsencrypt():
|
||||||
|
"""Configure letsencrypt."""
|
||||||
|
with sudo():
|
||||||
|
run("add-apt-repository --yes ppa:certbot/certbot")
|
||||||
|
run("apt update")
|
||||||
|
run("apt install -y certbot")
|
||||||
|
mkdir("/var/www/letsencrypt/.well-known/acme-challenge")
|
||||||
|
domains = ",".join(list(config.domains))
|
||||||
|
certbot_conf = template("remote/certbot.ini", domains=domains)
|
||||||
|
put(certbot_conf, "/var/www/certbot.ini")
|
||||||
|
run("certbot certonly -c /var/www/certbot.ini --non-interactive " "--agree-tos")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def bootstrap():
|
||||||
|
"""Bootstrap the system."""
|
||||||
|
system()
|
||||||
|
venv()
|
||||||
|
service()
|
||||||
|
http()
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def cli(command):
|
||||||
|
"""Run the copanier executable on the remote server.
|
||||||
|
"""
|
||||||
|
with sudo(user="copanier"), env(COPANIER_DATA_ROOT="/srv/copanier/data"):
|
||||||
|
run(f"/srv/copanier/venv/bin/copanier {command}")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def service():
|
||||||
|
"""Deploy/update the copanier systemd service."""
|
||||||
|
with sudo():
|
||||||
|
put("remote/copanier.service", "/etc/systemd/system/copanier.service")
|
||||||
|
systemctl("enable copanier.service")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def deploy():
|
||||||
|
"""Deploy/update the copanier code base."""
|
||||||
|
with sudo(user="copanier"):
|
||||||
|
put("remote/gunicorn.conf", "/srv/copanier/gunicorn.conf")
|
||||||
|
pip("install gunicorn")
|
||||||
|
base = "https://framagit.org/ybon/copanier"
|
||||||
|
pip(f"install -U git+{base}")
|
||||||
|
restart()
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def restart(*services):
|
||||||
|
"""Restart the systemd services."""
|
||||||
|
services = services or ["copanier", "nginx"]
|
||||||
|
with sudo():
|
||||||
|
systemctl(f"restart {' '.join(services)}")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def systemctl(*args):
|
||||||
|
"""Run a systemctl command on the remote server.
|
||||||
|
|
||||||
|
:command: the systemctl command to run.
|
||||||
|
"""
|
||||||
|
run(f'systemctl {" ".join(args)}')
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def logs(lines=50):
|
||||||
|
"""Display the copanier logs.
|
||||||
|
|
||||||
|
:lines: number of lines to retrieve
|
||||||
|
"""
|
||||||
|
with sudo():
|
||||||
|
run(f"journalctl --lines {lines} --unit copanier --follow")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def status():
|
||||||
|
"""Get the services status."""
|
||||||
|
systemctl("status nginx copanier")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def access_logs():
|
||||||
|
"""See the nginx access logs."""
|
||||||
|
with sudo():
|
||||||
|
run("tail -F /var/log/nginx/access.log")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def error_logs():
|
||||||
|
"""See the nginx error logs."""
|
||||||
|
with sudo():
|
||||||
|
run("tail -F /var/log/nginx/error.log")
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.cli
|
||||||
|
def upload_env():
|
||||||
|
"""Upload environment vars to the server.
|
||||||
|
|
||||||
|
Use those to deal with info not commitable.
|
||||||
|
"""
|
||||||
|
vars_ = {
|
||||||
|
"COPANIER_DATA_ROOT": "/srv/copanier/data",
|
||||||
|
"COPANIER_SEND_MAILS": "1",
|
||||||
|
"COPANIER_SMTP_PASSWORD": None,
|
||||||
|
"COPANIER_SMTP_LOGIN": None,
|
||||||
|
}
|
||||||
|
content = ""
|
||||||
|
for key, value in vars_.items():
|
||||||
|
try:
|
||||||
|
content += "{}={}\n".format(key, value or os.environ[key])
|
||||||
|
except KeyError:
|
||||||
|
sys.exit(f"The {key} environment variable does not exist.")
|
||||||
|
path = "/srv/copanier/env"
|
||||||
|
if exists(path):
|
||||||
|
run(f"cat {path}")
|
||||||
|
put(StringIO(content), path)
|
||||||
|
|
||||||
|
|
||||||
|
@minicli.wrap
|
||||||
|
def wrapper(hostname, configpath):
|
||||||
|
with connect(hostname=hostname, configpath=configpath):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
minicli.run(hostname="qa", configpath="remote/config.yml")
|
4
remote/certbot.ini
Normal file
4
remote/certbot.ini
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
authenticator = webroot
|
||||||
|
webroot-path = /var/www/letsencrypt
|
||||||
|
domains = $$domains
|
||||||
|
email = yohanboniface@free.fr
|
4
remote/config.yml
Normal file
4
remote/config.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
domains:
|
||||||
|
- copanier.epinamap.org
|
||||||
|
username: ubuntu
|
||||||
|
hostname: 185.145.251.38
|
17
remote/copanier.service
Normal file
17
remote/copanier.service
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[Unit]
|
||||||
|
Description=COPANIER
|
||||||
|
After=syslog.target network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=copanier
|
||||||
|
ExecStart=/srv/copanier/venv/bin/gunicorn copanier:app --config gunicorn.conf
|
||||||
|
WorkingDirectory=/srv/copanier/
|
||||||
|
EnvironmentFile=/srv/copanier/env
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
SyslogIdentifier=COPANIER
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
4
remote/gunicorn.conf
Normal file
4
remote/gunicorn.conf
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
worker_class = 'roll.worker.Worker'
|
||||||
|
workers = 4
|
||||||
|
bind = 'unix:/tmp/copanier.sock'
|
||||||
|
log_level = 'debug'
|
5
remote/letsencrypt.conf
Normal file
5
remote/letsencrypt.conf
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
location ^~ /.well-known/acme-challenge/ {
|
||||||
|
default_type "text/plain";
|
||||||
|
root /var/www/letsencrypt;
|
||||||
|
auth_basic off;
|
||||||
|
}
|
14
remote/nginx-http.conf
Normal file
14
remote/nginx-http.conf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
upstream copanier {
|
||||||
|
server unix:///tmp/copanier.sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name $${domains};
|
||||||
|
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 1M;
|
||||||
|
|
||||||
|
include /etc/nginx/snippets/letsencrypt.conf;
|
||||||
|
include /etc/nginx/snippets/copanier.conf;
|
||||||
|
}
|
40
remote/nginx-https.conf
Normal file
40
remote/nginx-https.conf
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
upstream copanier {
|
||||||
|
server unix:///tmp/copanier.sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
server_name $${domains};
|
||||||
|
include /etc/nginx/snippets/letsencrypt.conf;
|
||||||
|
|
||||||
|
location /nginx_status {
|
||||||
|
stub_status on;
|
||||||
|
access_log off;
|
||||||
|
allow 127.0.0.1;
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
server {
|
||||||
|
server_name $${domains};
|
||||||
|
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/$$domain/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/$$domain/privkey.pem;
|
||||||
|
ssl_trusted_certificate /etc/letsencrypt/live/$$domain/fullchain.pem;
|
||||||
|
include /etc/nginx/snippets/ssl.conf;
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 1M;
|
||||||
|
|
||||||
|
include /etc/nginx/snippets/letsencrypt.conf;
|
||||||
|
|
||||||
|
include /etc/nginx/snippets/copanier.conf;
|
||||||
|
}
|
6
remote/nginx-snippet.conf
Normal file
6
remote/nginx-snippet.conf
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
root /srv/copanier;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://copanier/;
|
||||||
|
}
|
||||||
|
|
5
remote/ssl-renew
Normal file
5
remote/ssl-renew
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
certbot -c /srv/eurordis/certbot.ini renew
|
||||||
|
|
||||||
|
systemctl reload nginx
|
15
remote/ssl.conf
Normal file
15
remote/ssl.conf
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
ssl_session_timeout 1d;
|
||||||
|
ssl_session_cache shared:SSL:50m;
|
||||||
|
ssl_session_tickets off;
|
||||||
|
|
||||||
|
ssl_protocols TLSv1.2;
|
||||||
|
ssl_ciphers EECDH+AESGCM:EECDH+AES;
|
||||||
|
ssl_ecdh_curve secp384r1;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
ssl_stapling on;
|
||||||
|
ssl_stapling_verify on;
|
||||||
|
|
||||||
|
add_header Strict-Transport-Security "max-age=15768000; includeSubdomains; preload";
|
||||||
|
add_header X-Frame-Options DENY;
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
4
requirements-dev.txt
Normal file
4
requirements-dev.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
hupper
|
||||||
|
pytest
|
||||||
|
pytest-asyncio
|
||||||
|
usine
|
7
requirements.txt
Normal file
7
requirements.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Jinja2==2.10
|
||||||
|
minicli==0.4.4
|
||||||
|
openpyxl==2.6.1
|
||||||
|
PyJWT==1.7.1
|
||||||
|
PyYAML==5.1
|
||||||
|
roll==0.10.1
|
||||||
|
ujson==1.35
|
|
@ -1,7 +1,7 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = kaba
|
name = copanier
|
||||||
version = 0.0.1
|
version = 0.0.1
|
||||||
|
|
||||||
[options.entry_points]
|
[options.entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
kaba = kaba:main
|
copanier = copanier:main
|
||||||
|
|
|
@ -5,14 +5,14 @@ import pytest
|
||||||
from roll.extensions import traceback
|
from roll.extensions import traceback
|
||||||
from roll.testing import Client as BaseClient
|
from roll.testing import Client as BaseClient
|
||||||
|
|
||||||
from kaba import app as kaba_app
|
from copanier import app as copanier_app
|
||||||
from kaba import config as kconfig
|
from copanier import config as kconfig
|
||||||
from kaba import create_token
|
from copanier import create_token
|
||||||
from kaba.models import Delivery, Person
|
from copanier.models import Delivery, Person
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
os.environ["KABA_DATA_ROOT"] = "tmp/db"
|
os.environ["COPANIER_DATA_ROOT"] = "tmp/db"
|
||||||
kconfig.init()
|
kconfig.init()
|
||||||
assert str(kconfig.DATA_ROOT) == "tmp/db"
|
assert str(kconfig.DATA_ROOT) == "tmp/db"
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@ def client(app, event_loop):
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def app(): # Requested by Roll testing utilities.
|
def app(): # Requested by Roll testing utilities.
|
||||||
traceback(kaba_app)
|
traceback(copanier_app)
|
||||||
return kaba_app
|
return copanier_app
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from kaba import config
|
from copanier import config
|
||||||
|
|
||||||
|
|
||||||
def test_config_should_read_from_env():
|
def test_config_should_read_from_env():
|
||||||
old_root = os.environ.get("KABA_DATA_ROOT", "")
|
old_root = os.environ.get("COPANIER_DATA_ROOT", "")
|
||||||
old_secret = os.environ.get("KABA_SECRET", "")
|
old_secret = os.environ.get("COPANIER_SECRET", "")
|
||||||
assert config.SECRET == "sikretfordevonly"
|
assert config.SECRET == "sikretfordevonly"
|
||||||
assert isinstance(config.DATA_ROOT, Path)
|
assert isinstance(config.DATA_ROOT, Path)
|
||||||
os.environ["KABA_DATA_ROOT"] = "changeme"
|
os.environ["COPANIER_DATA_ROOT"] = "changeme"
|
||||||
os.environ["KABA_SECRET"] = "ultrasecret"
|
os.environ["COPANIER_SECRET"] = "ultrasecret"
|
||||||
config.init()
|
config.init()
|
||||||
assert config.SECRET == "ultrasecret"
|
assert config.SECRET == "ultrasecret"
|
||||||
assert isinstance(config.DATA_ROOT, Path)
|
assert isinstance(config.DATA_ROOT, Path)
|
||||||
assert str(config.DATA_ROOT) == "changeme"
|
assert str(config.DATA_ROOT) == "changeme"
|
||||||
if old_root:
|
if old_root:
|
||||||
os.environ["KABA_DATA_ROOT"] = old_root
|
os.environ["COPANIER_DATA_ROOT"] = old_root
|
||||||
else:
|
else:
|
||||||
del os.environ["KABA_DATA_ROOT"]
|
del os.environ["COPANIER_DATA_ROOT"]
|
||||||
if old_secret:
|
if old_secret:
|
||||||
os.environ["KABA_SECRET"] = old_secret
|
os.environ["COPANIER_SECRET"] = old_secret
|
||||||
else:
|
else:
|
||||||
del os.environ["KABA_SECRET"]
|
del os.environ["COPANIER_SECRET"]
|
||||||
config.init()
|
config.init()
|
||||||
|
|
|
@ -2,7 +2,7 @@ from datetime import datetime, timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from kaba.models import Delivery, Product, Person, Order, ProductOrder
|
from copanier.models import Delivery, Product, Person, Order, ProductOrder
|
||||||
|
|
||||||
|
|
||||||
now = datetime.now
|
now = datetime.now
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from kaba.models import Delivery
|
from copanier.models import Delivery
|
||||||
|
|
||||||
pytestmark = pytest.mark.asyncio
|
pytestmark = pytest.mark.asyncio
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue