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

update db schema

parent 331d6350
Branches
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 Aplikace pro transparentní evidenci smluv a informací s nimi spojených.
information about them.
## Basic requirements ## Struktura projektu
- `make` ```
- `python`, 3.9 or newer .
- `python-virtualenv` ├── registry = Nastavení projektu, URLs.
- A PostgreSQL server ├── 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 ```bash
make venv # Create virtual environment $ make venv
make install # Install Python and Node.js dependencies
make build # Build static files
``` ```
## 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 ```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 ```bash
source .venv/bin/activate # Load the virtual environment $ make hooks
python manage.py runserver # [ Your settings here ]
``` ```
"""
Production settings.
"""
from .base import * 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.db.models.deletion
import django.utils.timezone import django.utils.timezone
...@@ -145,18 +145,6 @@ class Migration(migrations.Migration): ...@@ -145,18 +145,6 @@ class Migration(migrations.Migration):
"contains_nda", "contains_nda",
models.BooleanField(default=False, verbose_name="Obsahuje 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", "all_parties_sign_date",
models.DateField(verbose_name="Datum podpisu všech stran"), models.DateField(verbose_name="Datum podpisu všech stran"),
...@@ -165,7 +153,7 @@ class Migration(migrations.Migration): ...@@ -165,7 +153,7 @@ class Migration(migrations.Migration):
"valid_start_date", "valid_start_date",
models.DateField(verbose_name="Začátek účinnosti"), 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", "legal_state",
models.CharField( models.CharField(
...@@ -208,6 +196,7 @@ class Migration(migrations.Migration): ...@@ -208,6 +196,7 @@ class Migration(migrations.Migration):
"publishing_rejection_comment", "publishing_rejection_comment",
models.CharField( models.CharField(
blank=True, blank=True,
help_text="Obsah není veřejně přístupný.",
max_length=65536, max_length=65536,
null=True, null=True,
verbose_name="Důvod nezveřejnění", verbose_name="Důvod nezveřejnění",
...@@ -232,14 +221,25 @@ class Migration(migrations.Migration): ...@@ -232,14 +221,25 @@ class Migration(migrations.Migration):
"summary", "summary",
models.CharField( models.CharField(
blank=True, blank=True,
help_text="Obsah není veřejně přístupný.",
max_length=65536, max_length=65536,
null=True, null=True,
verbose_name="Rekapitulace", verbose_name="Rekapitulace",
), ),
), ),
( (
"contract_file", "anonymized_contract_file",
models.FileField(upload_to="", verbose_name="Smlouva (PDF)"), 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", "expected_cost_total",
...@@ -296,17 +296,31 @@ class Migration(migrations.Migration): ...@@ -296,17 +296,31 @@ class Migration(migrations.Migration):
("name", models.CharField(max_length=256, verbose_name="Jméno")), ("name", models.CharField(max_length=256, verbose_name="Jméno")),
( (
"is_legal_entity", "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", "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", "address_district",
models.CharField(max_length=256, verbose_name="Obec"), 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", "address_country",
django_countries.fields.CountryField( django_countries.fields.CountryField(
...@@ -409,26 +423,72 @@ class Migration(migrations.Migration): ...@@ -409,26 +423,72 @@ class Migration(migrations.Migration):
verbose_name="ID", 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", "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", "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", "address_country",
django_countries.fields.CountryField( django_countries.fields.CountryField(
max_length=2, verbose_name="Země" default="CZ", max_length=2, verbose_name="Země"
), ),
), ),
( (
"ico_number", "ico_number",
models.CharField( 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")), ("color", models.CharField(max_length=6, verbose_name="Barva")),
...@@ -493,19 +553,61 @@ class Migration(migrations.Migration): ...@@ -493,19 +553,61 @@ class Migration(migrations.Migration):
"verbose_name_plural": "Poznámky ke smlouvě", "verbose_name_plural": "Poznámky ke smlouvě",
}, },
), ),
migrations.AddField( migrations.CreateModel(
model_name="contract", name="ContractLocalSignature",
name="external_signer", fields=[
field=models.ForeignKey( (
"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, on_delete=django.db.models.deletion.CASCADE,
to="shared.contractexternalsigner", to="shared.contractexternalsigner",
), ),
), ),
],
),
migrations.AddField(
model_name="contract",
name="external_signature",
field=models.ManyToManyField(to="shared.contractexternalsignature"),
),
migrations.AddField( migrations.AddField(
model_name="contract", model_name="contract",
name="filing_area", name="filing_area",
field=models.ForeignKey( field=models.ForeignKey(
blank=True, blank=True,
help_text="Obsah není veřejně přístupný.",
null=True, null=True,
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
to="shared.contractfilingarea", to="shared.contractfilingarea",
...@@ -518,11 +620,8 @@ class Migration(migrations.Migration): ...@@ -518,11 +620,8 @@ class Migration(migrations.Migration):
), ),
migrations.AddField( migrations.AddField(
model_name="contract", model_name="contract",
name="local_signer", name="local_signature",
field=models.ForeignKey( field=models.ManyToManyField(to="shared.contractlocalsignature"),
on_delete=django.db.models.deletion.CASCADE,
to="shared.contractlocalsigner",
),
), ),
migrations.AddField( migrations.AddField(
model_name="contract", model_name="contract",
...@@ -539,6 +638,7 @@ class Migration(migrations.Migration): ...@@ -539,6 +638,7 @@ class Migration(migrations.Migration):
model_name="contract", model_name="contract",
name="public_status_set_by", name="public_status_set_by",
field=models.ForeignKey( field=models.ForeignKey(
help_text="Obsah není veřejně přístupný.",
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
related_name="public_status_altered_contracts", related_name="public_status_altered_contracts",
to=settings.AUTH_USER_MODEL, to=settings.AUTH_USER_MODEL,
...@@ -558,6 +658,7 @@ class Migration(migrations.Migration): ...@@ -558,6 +658,7 @@ class Migration(migrations.Migration):
model_name="contract", model_name="contract",
name="uploaded_by", name="uploaded_by",
field=models.ForeignKey( field=models.ForeignKey(
help_text="Informace není veřejně přístupná.",
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
related_name="uploaded_contracts", related_name="uploaded_contracts",
to=settings.AUTH_USER_MODEL, 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): ...@@ -17,11 +17,13 @@ class ContractExternalSigner(models.Model):
is_legal_entity = models.BooleanField( is_legal_entity = models.BooleanField(
verbose_name="Je právnická osoba", 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( address_street_with_number = models.CharField(
max_length=256, max_length=256,
verbose_name="Ulice, č.p.", verbose_name="Ulice, č.p.",
help_text="Viditelné pouze u právnických osob.",
) # WARNING: Legal entity status dependent! ) # WARNING: Legal entity status dependent!
address_district = models.CharField( address_district = models.CharField(
...@@ -32,6 +34,7 @@ class ContractExternalSigner(models.Model): ...@@ -32,6 +34,7 @@ class ContractExternalSigner(models.Model):
address_zip = models.CharField( address_zip = models.CharField(
max_length=16, max_length=16,
verbose_name="PSČ", verbose_name="PSČ",
help_text="Viditelné pouze u právnických osob.",
) # WARNING: Legal entity status dependent! ) # WARNING: Legal entity status dependent!
address_country = CountryField( address_country = CountryField(
...@@ -77,6 +80,17 @@ class ContractExternalSigner(models.Model): ...@@ -77,6 +80,17 @@ class ContractExternalSigner(models.Model):
verbose_name_plural = "Druhé smluvní strany" 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): class ContractLocalSigner(models.Model):
name = models.CharField( name = models.CharField(
max_length=256, max_length=256,
...@@ -147,6 +161,17 @@ class ContractLocalSigner(models.Model): ...@@ -147,6 +161,17 @@ class ContractLocalSigner(models.Model):
verbose_name_plural = "Naše smlouvní strany" 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): class ContractSubtype(models.Model):
name = models.CharField( name = models.CharField(
max_length=32, max_length=32,
...@@ -209,37 +234,19 @@ class Contract(models.Model): ...@@ -209,37 +234,19 @@ class Contract(models.Model):
verbose_name="Obsahuje NDA", verbose_name="Obsahuje NDA",
) )
is_anonymized = models.BooleanField( external_signature = models.ManyToManyField(ContractExternalSignature)
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,
)
local_signer_signature_date = models.DateField( local_signature = models.ManyToManyField(ContractLocalSignature)
verbose_name="Datum podpisu naší strany",
)
all_parties_sign_date = models.DateField( all_parties_sign_date = models.DateField(
verbose_name="Datum podpisu všech stran", verbose_name="Datum podpisu všech stran",
) # WARNING: Exclude in admin, autofill ) # 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( valid_end_date = models.DateField(
verbose_name="Začátek platnosti", verbose_name="Konec platnosti",
) )
uploaded_by = models.ForeignKey( uploaded_by = models.ForeignKey(
...@@ -247,7 +254,8 @@ class Contract(models.Model): ...@@ -247,7 +254,8 @@ class Contract(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name="uploaded_contracts", related_name="uploaded_contracts",
verbose_name="Nahráno uživatelem", verbose_name="Nahráno uživatelem",
) help_text="Informace není veřejně přístupná.",
) # WARNING: exclude in admin
class LegalStates(models.TextChoices): class LegalStates(models.TextChoices):
VALID = "valid", "Platná" VALID = "valid", "Platná"
...@@ -289,14 +297,16 @@ class Contract(models.Model): ...@@ -289,14 +297,16 @@ class Contract(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name="public_status_altered_contracts", related_name="public_status_altered_contracts",
verbose_name="Zveřejněno / nezveřejněno uživatelem", 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( publishing_rejection_comment = models.CharField(
max_length=65536, max_length=65536,
blank=True, blank=True,
null=True, null=True,
verbose_name="Důvod nezveřejnění", verbose_name="Důvod nezveřejnění",
) help_text="Obsah není veřejně přístupný.",
) # WARNING: exclude in admin
tender_url = models.URLField( tender_url = models.URLField(
max_length=256, max_length=256,
...@@ -317,10 +327,16 @@ class Contract(models.Model): ...@@ -317,10 +327,16 @@ class Contract(models.Model):
blank=True, blank=True,
null=True, null=True,
verbose_name="Rekapitulace", 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( original_contract_file = models.FileField(
verbose_name="Smlouva (PDF)", verbose_name="Originální verze smlouvy (PDF)",
help_text="Obsah není veřejně přístupný.",
) )
primary_contract = models.ForeignKey( primary_contract = models.ForeignKey(
...@@ -358,6 +374,7 @@ class Contract(models.Model): ...@@ -358,6 +374,7 @@ class Contract(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
blank=True, blank=True,
null=True, null=True,
help_text="Obsah není veřejně přístupný.",
) # WARNING: Dependent on the type! ) # WARNING: Dependent on the type!
class Meta: class Meta:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment