Skip to content
Snippets Groups Projects
Commit e1cdf4a8 authored by Tomáš's avatar Tomáš
Browse files

update db schema

parent 331d6350
No related branches found
No related tags found
No related merge requests found
# Contract registry
# Registr smluv
The Czech Pirate Party's transparent evidence system for its contracts and
information about them.
Aplikace pro transparentní evidenci smluv a informací s nimi spojených.
## Basic requirements
- `make`
- `python`, 3.9 or newer
- `python-virtualenv`
- A PostgreSQL server
## Struktura projektu
```
.
├── registry = Nastavení projektu, URLs.
├── shared = Sdílené modely, templaty a statické soubory pro všechny ostatní aplikace.
├── static_src = Zdrojové CSS a JS, které příkazem 'make build' buildujeme.
├── requirements = Pythonové závislosti z PyPI.
└── env.example = Příklad .env souboru.
```
## Konfigurace
Je třeba definovat minimálně následující environment proměnné:
| proměnná | popis |
| --- | --- |
| `DATABASE_URL` | URL pro připojení k databázi ve formátu `postgresql://username:password@host:5432/database_name` |
| `SECRET_KEY` | Tajný klíč např. pro šifrování |
| `DEFAULT_LOCAL_SIGNER_NAME` | Defaultní jméno naší podepisující strany |
| `DEFAULT_LCOAL_SIGNER_STREET` | Defaultní ulice a č.p. naší podepisující strany |
| `DEFAULT_LOCAL_SIGNER_ZIP` | Defaultní PSČ naší podepisující strany |
| `DEFAULT_LOCAL_SIGNER_DISTRICT` | Defaultní obec naší podepisující strany |
| `DEFAULT_LOCAL_SIGNER_COUNTRY` | Defaultní země naší podepisující strany, např. `CZ`, `DE` |
| `DEFAULT_LOCAL_SIGNER_ICO_NUMBER` | Defaultní IČO naší podepisující strany |
V produkci je potřeba:
| proměnná | popis |
| --- | --- |
| `ALLOWED_HOSTS` | Seznam domén, skrz které se na server lze připojovat. [Více info](https://docs.djangoproject.com/en/4.1/ref/settings/#allowed-hosts) |
## Vývoj
V produkci používáme Docker. Při vývoji se hodí přiložený `Makefile`, pro automatizování často prováděných akcí. Pro nápovědu zavolej:
## Setup
```bash
$ make help
```
Copy `env.example` to `.env`.
### Lokální setup
Set the ``DATABASE_URL`` environment variable in `.env` to one you can access your database server with. The format should be as per RFC 1738, such as `postgresql://login:password@localhost:5432/database_name`. It's also important to change the ``SECRET_KEY`` variable.
Požadavky:
- Python 3.9+
- Linuxové prostředí, na Windows netestováno
Then, run the following commands:
Zkopíruj `env.example` do `.env`, nastav potřebné proměnné.
Vytvoř virtualenv:
```bash
make venv # Create virtual environment
make install # Install Python and Node.js dependencies
make build # Build static files
$ make venv
```
## Running
Vytvoří virtualenv ve složce `.venv`. Předpokládá že výchozí `python` v terminálu
je Python 3. Pokud tomu tak není, použij třeba [Pyenv](https://github.com/pyenv/pyenv)
pro instalaci více verzí Pythonu bez rizika rozbití systému.
### Aktivace virtualenvu
To run with the default settings on the port set in `Makefile`, run:
Před prací na projektu je třeba aktivovat virtualenv. To bohužel nejde dělat
pomocí nástroje `make`. Je třeba zavolat příkaz:
```bash
make run
$ source .venv/bin/activate
```
For more customization, it's better practice to run the server starting command directly:
Pro shell lze vytvořit alias. Do `~/.bash_profile`, `~/.zshrc` nebo jiného
konfiguračního souboru dle tvého shellu přidej:
```bash
$ alias senv='source .venv/bin/activate'
```
A pak můžeš virtualenv aktivovat pouze jednoduchým voláním:
```bash
$ senv
```
Pro sofistikovanější řešení, které aktivuje virtualenv při změně adresáře na
adresář s projektem, slouží nástroj [direnv](https://direnv.net/).
Deaktivace virtualenv se dělá příkazem:
```bash
$ deactivate
```
### Instalace závislostí
Spusť:
```bash
$ make install
```
Tím se nainstalují Pythonové závislosti a virtuální Node.js 19x nutné k buildu statických souborů.
### Build statických souborů
Spusť:
```bash
$ make build
```
Tím se vytvoří CSS a JS soubory, které se dají použít v prohlížečovém prostředí.
### Spuštění development serveru
Django development server na portu `8012` se spustí příkazem:
```bash
$ make run
```
Poté můžeš web otevřít na adrese [http://localhost:8012](http://localhost:8012).
### Code quality
K formátování kódu se používá [black](https://github.com/psf/black). Doporučujeme
ho nainstalovat do tvého editoru, aby soubory přeformátoval po uložení.
Přeformátování kódu nástrojem `black` je součástí `pre-commit` hooks (viz níže).
Součástí `pre-commit` hooků je také automatické seřazení importů v Pythonních
souborech nástrojem [isort](https://github.com/timothycrosley/isort/).
### Pre-commit hooky
Použivá se [pre-commit](https://pre-commit.com/) framework pro management git
pre-commit hooks.
Máš-li pre-commit framework [nainstalovaný](https://pre-commit.com/#installation),
spusť příkaz:
```bash
$ make install-hooks
```
Ten naisntaluje hooky pro projekt. A poté při každém commitu dojde k požadovaným
akcím na změněných souborech.
Ručně se dají hooky na všechny soubory spustit příkazem:
```bash
source .venv/bin/activate # Load the virtual environment
python manage.py runserver # [ Your settings here ]
$ make hooks
```
"""
Production settings.
"""
from .base import *
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS")
MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware")
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
gunicorn==20.1.0
whitenoise==6.3.0
# Generated by Django 4.1.4 on 2023-02-03 04:50
# Generated by Django 4.1.4 on 2023-02-03 15:13
import django.db.models.deletion
import django.utils.timezone
......@@ -145,18 +145,6 @@ class Migration(migrations.Migration):
"contains_nda",
models.BooleanField(default=False, verbose_name="Obsahuje NDA"),
),
(
"is_anonymized",
models.BooleanField(default=False, verbose_name="Je anonymizovaná"),
),
(
"external_signer_signature_date",
models.DateField(verbose_name="Datum podpisu druhé strany"),
),
(
"local_signer_signature_date",
models.DateField(verbose_name="Datum podpisu naší strany"),
),
(
"all_parties_sign_date",
models.DateField(verbose_name="Datum podpisu všech stran"),
......@@ -165,7 +153,7 @@ class Migration(migrations.Migration):
"valid_start_date",
models.DateField(verbose_name="Začátek účinnosti"),
),
("valid_end_date", models.DateField(verbose_name="Začátek platnosti")),
("valid_end_date", models.DateField(verbose_name="Konec platnosti")),
(
"legal_state",
models.CharField(
......@@ -208,6 +196,7 @@ class Migration(migrations.Migration):
"publishing_rejection_comment",
models.CharField(
blank=True,
help_text="Obsah není veřejně přístupný.",
max_length=65536,
null=True,
verbose_name="Důvod nezveřejnění",
......@@ -232,14 +221,25 @@ class Migration(migrations.Migration):
"summary",
models.CharField(
blank=True,
help_text="Obsah není veřejně přístupný.",
max_length=65536,
null=True,
verbose_name="Rekapitulace",
),
),
(
"contract_file",
models.FileField(upload_to="", verbose_name="Smlouva (PDF)"),
"anonymized_contract_file",
models.FileField(
upload_to="", verbose_name="Anonymizovaná smlouva (PDF)"
),
),
(
"original_contract_file",
models.FileField(
help_text="Obsah není veřejně přístupný.",
upload_to="",
verbose_name="Originální verze smlouvy (PDF)",
),
),
(
"expected_cost_total",
......@@ -296,17 +296,31 @@ class Migration(migrations.Migration):
("name", models.CharField(max_length=256, verbose_name="Jméno")),
(
"is_legal_entity",
models.BooleanField(verbose_name="Je právnická osoba"),
models.BooleanField(
help_text="Důležité označit správně! Pokud není osoba právnická, zveřejňujeme pouze obec a zemi.",
verbose_name="Je právnická osoba",
),
),
(
"address_street_with_number",
models.CharField(max_length=256, verbose_name="Ulice, č.p."),
models.CharField(
help_text="Viditelné pouze u právnických osob.",
max_length=256,
verbose_name="Ulice, č.p.",
),
),
(
"address_district",
models.CharField(max_length=256, verbose_name="Obec"),
),
("address_zip", models.CharField(max_length=16, verbose_name="PSČ")),
(
"address_zip",
models.CharField(
help_text="Viditelné pouze u právnických osob.",
max_length=16,
verbose_name="PSČ",
),
),
(
"address_country",
django_countries.fields.CountryField(
......@@ -409,26 +423,72 @@ class Migration(migrations.Migration):
verbose_name="ID",
),
),
("name", models.CharField(max_length=256, verbose_name="Jméno")),
(
"name",
models.CharField(
default="Česká pirátská strana",
max_length=256,
verbose_name="Jméno",
),
),
(
"address_street_with_number",
models.CharField(max_length=256, verbose_name="Ulice, č.p."),
models.CharField(
default="Na Moráni 360/3",
max_length=256,
verbose_name="Ulice, č.p.",
),
),
(
"address_district",
models.CharField(max_length=256, verbose_name="Obec"),
models.CharField(
default="Praha 2", max_length=256, verbose_name="Obec"
),
),
(
"address_zip",
models.CharField(
default="128 00", max_length=16, verbose_name="PSČ"
),
),
("address_zip", models.CharField(max_length=16, verbose_name="PSČ")),
(
"address_country",
django_countries.fields.CountryField(
max_length=2, verbose_name="Země"
default="CZ", max_length=2, verbose_name="Země"
),
),
(
"ico_number",
models.CharField(
blank=True, max_length=16, null=True, verbose_name="IČO"
blank=True,
default="71339698",
max_length=16,
null=True,
verbose_name="IČO",
),
),
(
"representative_name",
models.CharField(
blank=True, max_length=256, null=True, verbose_name="Zástupce"
),
),
(
"representative_role",
models.CharField(
blank=True,
max_length=256,
null=True,
verbose_name="Funkce zástupce",
),
),
(
"department",
models.CharField(
blank=True,
max_length=128,
null=True,
verbose_name="Organizační složka",
),
),
("color", models.CharField(max_length=6, verbose_name="Barva")),
......@@ -493,19 +553,61 @@ class Migration(migrations.Migration):
"verbose_name_plural": "Poznámky ke smlouvě",
},
),
migrations.CreateModel(
name="ContractLocalSignature",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("date", models.DateField(verbose_name="Datum podpisu")),
(
"signer",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="shared.contractlocalsigner",
),
),
],
),
migrations.CreateModel(
name="ContractExternalSignature",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("date", models.DateField(verbose_name="Datum podpisu")),
(
"signer",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="shared.contractexternalsigner",
),
),
],
),
migrations.AddField(
model_name="contract",
name="external_signer",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="shared.contractexternalsigner",
),
name="external_signature",
field=models.ManyToManyField(to="shared.contractexternalsignature"),
),
migrations.AddField(
model_name="contract",
name="filing_area",
field=models.ForeignKey(
blank=True,
help_text="Obsah není veřejně přístupný.",
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="shared.contractfilingarea",
......@@ -518,11 +620,8 @@ class Migration(migrations.Migration):
),
migrations.AddField(
model_name="contract",
name="local_signer",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="shared.contractlocalsigner",
),
name="local_signature",
field=models.ManyToManyField(to="shared.contractlocalsignature"),
),
migrations.AddField(
model_name="contract",
......@@ -539,6 +638,7 @@ class Migration(migrations.Migration):
model_name="contract",
name="public_status_set_by",
field=models.ForeignKey(
help_text="Obsah není veřejně přístupný.",
on_delete=django.db.models.deletion.CASCADE,
related_name="public_status_altered_contracts",
to=settings.AUTH_USER_MODEL,
......@@ -558,6 +658,7 @@ class Migration(migrations.Migration):
model_name="contract",
name="uploaded_by",
field=models.ForeignKey(
help_text="Informace není veřejně přístupná.",
on_delete=django.db.models.deletion.CASCADE,
related_name="uploaded_contracts",
to=settings.AUTH_USER_MODEL,
......
# Generated by Django 4.1.4 on 2023-02-03 05:01
import django_countries.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("shared", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="contractlocalsigner",
name="department",
field=models.CharField(
blank=True, max_length=128, null=True, verbose_name="Organizační složka"
),
),
migrations.AddField(
model_name="contractlocalsigner",
name="representative_name",
field=models.CharField(
blank=True, max_length=256, null=True, verbose_name="Zástupce"
),
),
migrations.AddField(
model_name="contractlocalsigner",
name="representative_role",
field=models.CharField(
blank=True, max_length=256, null=True, verbose_name="Funkce zástupce"
),
),
migrations.AlterField(
model_name="contractlocalsigner",
name="address_country",
field=django_countries.fields.CountryField(
default="CZ", max_length=2, verbose_name="Země"
),
),
migrations.AlterField(
model_name="contractlocalsigner",
name="address_district",
field=models.CharField(
default="Praha 2", max_length=256, verbose_name="Obec"
),
),
migrations.AlterField(
model_name="contractlocalsigner",
name="address_street_with_number",
field=models.CharField(
default="Na Moráni 360/3", max_length=256, verbose_name="Ulice, č.p."
),
),
migrations.AlterField(
model_name="contractlocalsigner",
name="address_zip",
field=models.CharField(default="128 00", max_length=16, verbose_name="PSČ"),
),
migrations.AlterField(
model_name="contractlocalsigner",
name="ico_number",
field=models.CharField(
blank=True,
default="71339698",
max_length=16,
null=True,
verbose_name="IČO",
),
),
migrations.AlterField(
model_name="contractlocalsigner",
name="name",
field=models.CharField(
default="Česká pirátská strana", max_length=256, verbose_name="Jméno"
),
),
]
......@@ -17,11 +17,13 @@ class ContractExternalSigner(models.Model):
is_legal_entity = models.BooleanField(
verbose_name="Je právnická osoba",
help_text="Důležité označit správně! Pokud není osoba právnická, zveřejňujeme pouze obec a zemi.",
)
address_street_with_number = models.CharField(
max_length=256,
verbose_name="Ulice, č.p.",
help_text="Viditelné pouze u právnických osob.",
) # WARNING: Legal entity status dependent!
address_district = models.CharField(
......@@ -32,6 +34,7 @@ class ContractExternalSigner(models.Model):
address_zip = models.CharField(
max_length=16,
verbose_name="PSČ",
help_text="Viditelné pouze u právnických osob.",
) # WARNING: Legal entity status dependent!
address_country = CountryField(
......@@ -77,6 +80,17 @@ class ContractExternalSigner(models.Model):
verbose_name_plural = "Druhé smluvní strany"
class ContractExternalSignature(models.Model):
signer = models.ForeignKey(
ContractExternalSigner,
on_delete=models.CASCADE,
)
date = models.DateField(
verbose_name="Datum podpisu",
)
class ContractLocalSigner(models.Model):
name = models.CharField(
max_length=256,
......@@ -147,6 +161,17 @@ class ContractLocalSigner(models.Model):
verbose_name_plural = "Naše smlouvní strany"
class ContractLocalSignature(models.Model):
signer = models.ForeignKey(
ContractLocalSigner,
on_delete=models.CASCADE,
)
date = models.DateField(
verbose_name="Datum podpisu",
)
class ContractSubtype(models.Model):
name = models.CharField(
max_length=32,
......@@ -209,37 +234,19 @@ class Contract(models.Model):
verbose_name="Obsahuje NDA",
)
is_anonymized = models.BooleanField(
default=False,
verbose_name="Je anonymizovaná",
) # WARNING: Seems to only be used for amendments
external_signer = models.ForeignKey(
ContractExternalSigner,
on_delete=models.CASCADE,
)
# NOTE: Should we allow these to be null, if a contract is logged before it is signed?
external_signer_signature_date = models.DateField(
verbose_name="Datum podpisu druhé strany",
)
local_signer = models.ForeignKey(
ContractLocalSigner,
on_delete=models.CASCADE,
)
external_signature = models.ManyToManyField(ContractExternalSignature)
local_signer_signature_date = models.DateField(
verbose_name="Datum podpisu naší strany",
)
local_signature = models.ManyToManyField(ContractLocalSignature)
all_parties_sign_date = models.DateField(
verbose_name="Datum podpisu všech stran",
) # WARNING: Exclude in admin, autofill
valid_start_date = models.DateField(verbose_name="Začátek účinnosti")
valid_start_date = models.DateField(
verbose_name="Začátek účinnosti",
)
valid_end_date = models.DateField(
verbose_name="Začátek platnosti",
verbose_name="Konec platnosti",
)
uploaded_by = models.ForeignKey(
......@@ -247,7 +254,8 @@ class Contract(models.Model):
on_delete=models.CASCADE,
related_name="uploaded_contracts",
verbose_name="Nahráno uživatelem",
)
help_text="Informace není veřejně přístupná.",
) # WARNING: exclude in admin
class LegalStates(models.TextChoices):
VALID = "valid", "Platná"
......@@ -289,14 +297,16 @@ class Contract(models.Model):
on_delete=models.CASCADE,
related_name="public_status_altered_contracts",
verbose_name="Zveřejněno / nezveřejněno uživatelem",
)
help_text="Obsah není veřejně přístupný.",
) # WARNING: exclude in admin
publishing_rejection_comment = models.CharField(
max_length=65536,
blank=True,
null=True,
verbose_name="Důvod nezveřejnění",
)
help_text="Obsah není veřejně přístupný.",
) # WARNING: exclude in admin
tender_url = models.URLField(
max_length=256,
......@@ -317,10 +327,16 @@ class Contract(models.Model):
blank=True,
null=True,
verbose_name="Rekapitulace",
help_text="Obsah není veřejně přístupný.",
)
anonymized_contract_file = models.FileField(
verbose_name="Anonymizovaná smlouva (PDF)",
)
contract_file = models.FileField(
verbose_name="Smlouva (PDF)",
original_contract_file = models.FileField(
verbose_name="Originální verze smlouvy (PDF)",
help_text="Obsah není veřejně přístupný.",
)
primary_contract = models.ForeignKey(
......@@ -358,6 +374,7 @@ class Contract(models.Model):
on_delete=models.CASCADE,
blank=True,
null=True,
help_text="Obsah není veřejně přístupný.",
) # WARNING: Dependent on the type!
class Meta:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment