Initial commit

This commit is contained in:
Johannes Schriewer 2020-08-04 02:42:42 +02:00
commit 85e8018333
53 changed files with 1633 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.mypy_cache
__pycache__
.python_version

17
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,17 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/__pycache__": true,
".mypy_cache": true
},
"python.linting.flake8Enabled": true,
"python.linting.mypyEnabled": true,
"python.linting.pylintEnabled": true,
"python.linting.flake8Args": [
"--max-line-length=120"
]
}

10
README.rst Normal file
View file

@ -0,0 +1,10 @@
## DB Hierarchy
- Workshop
- Area
- Area
- Box
- Box
- ...
- Item

0
inventory/__init__.py Normal file
View file

3
inventory/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View file

@ -0,0 +1,5 @@
from .distributor import DistributorAdmin
from .manufacturer import ManufacturerAdmin
from .layout import LayoutAdmin
from .item import ItemAdmin
from .containers import WorkshopAdmin, AreaAdmin, BoxAdmin

View file

@ -0,0 +1,69 @@
import nested_admin
from django.shortcuts import reverse
from django.contrib import admin
from django.conf import settings
from inventory.models import Workshop, Area, Box, Item, Documentation
class AreaInlineAdmin(admin.TabularInline):
model = Area
fk_name = 'container'
extra = 1
fields = ['name', 'description', 'index', 'layout']
class WorkshopAdmin(admin.ModelAdmin):
list_display = ['name', 'description']
readonly_fields = ['created_at', 'changed_at']
search_fields = ['name', 'description']
inlines = [AreaInlineAdmin]
def view_on_site(self, obj):
url = reverse('workshop-detail', kwargs={'pk': obj.id})
return settings.SERVER_URL + url
class BoxInlineAdmin(nested_admin.NestedTabularInline):
model = Box
fk_name = 'container'
extra = 1
fields = ['name', 'description', 'index', 'layout']
class AreaAdmin(nested_admin.NestedModelAdmin):
list_display = ['name', 'description', 'container']
readonly_fields = ['created_at', 'changed_at']
list_filter = ['container']
search_fields = ['name', 'description']
inlines = [BoxInlineAdmin]
def view_on_site(self, obj):
url = reverse('area-detail', kwargs={'pk': obj.id})
return settings.SERVER_URL + url
class DocumentationInlineAdmin(nested_admin.NestedTabularInline):
model = Documentation
extra = 1
class ItemInlineAdmin(nested_admin.NestedStackedInline):
model = Item
extra = 1
inlines = [DocumentationInlineAdmin]
class BoxAdmin(nested_admin.NestedModelAdmin):
list_display = ['name', 'description', 'container']
readonly_fields = ['created_at', 'changed_at']
list_filter = ['container']
search_fields = ['name', 'description']
inlines = [BoxInlineAdmin, ItemInlineAdmin]
def view_on_site(self, obj):
url = reverse('box-detail', kwargs={'pk': obj.id})
return settings.SERVER_URL + url
admin.site.register(Workshop, WorkshopAdmin)
admin.site.register(Area, AreaAdmin)
admin.site.register(Box, BoxAdmin)

View file

@ -0,0 +1,16 @@
from django.shortcuts import reverse
from django.contrib import admin
from django.conf import settings
from inventory.models import Distributor
class DistributorAdmin(admin.ModelAdmin):
readonly_fields = ['created_at', 'changed_at']
search_fields = ['name', 'description']
def view_on_site(self, obj):
url = reverse('distributor-detail', kwargs={'pk': obj.id})
return settings.SERVER_URL + url
admin.site.register(Distributor, DistributorAdmin)

25
inventory/admin/item.py Normal file
View file

@ -0,0 +1,25 @@
from django.shortcuts import reverse
from django.contrib import admin
from django.conf import settings
from inventory.models import Item, Documentation
class DocumentationInlineAdmin(admin.TabularInline):
model = Documentation
extra = 1
class ItemAdmin(admin.ModelAdmin):
list_display = ['container', 'index', 'name', 'description']
list_display_links = ['name', 'description']
list_filter = ['container']
search_fields = ['name', 'description']
readonly_fields = ['created_at', 'changed_at']
inlines = [DocumentationInlineAdmin]
def view_on_site(self, obj):
url = reverse('item-detail', kwargs={'pk': obj.id})
return settings.SERVER_URL + url
admin.site.register(Item, ItemAdmin)

38
inventory/admin/layout.py Normal file
View file

@ -0,0 +1,38 @@
import json
from django.shortcuts import reverse
from django.contrib import admin
from django.conf import settings
from django.contrib.postgres.fields import JSONField
from django.forms import widgets
from inventory.models import Layout
class PrettyJSONWidget(widgets.Textarea):
def format_value(self, value):
try:
value = json.dumps(json.loads(value), indent=4, sort_keys=True)
# these lines will try to adjust size of TextArea to fit to content
row_lengths = [len(r) for r in value.split('\n')]
self.attrs['rows'] = min(max(len(row_lengths) + 2, 10), 30)
self.attrs['cols'] = min(max(max(row_lengths) + 2, 40), 120)
return value
except Exception as e:
logger.warning("Error while formatting JSON: {}".format(e))
return super(PrettyJSONWidget, self).format_value(value)
class LayoutAdmin(admin.ModelAdmin):
readonly_fields = ['created_at', 'changed_at']
search_fields = ['name', 'description']
formfield_overrides = {
JSONField: {'widget': PrettyJSONWidget}
}
# def view_on_site(self, obj):
# url = reverse('layout-detail', kwargs={'id': obj.id})
# return settings.SERVER_URL + url
admin.site.register(Layout, LayoutAdmin)

View file

@ -0,0 +1,16 @@
from django.shortcuts import reverse
from django.contrib import admin
from django.conf import settings
from inventory.models import Manufacturer
class ManufacturerAdmin(admin.ModelAdmin):
readonly_fields = ['created_at', 'changed_at']
search_fields = ['name', 'description']
def view_on_site(self, obj):
url = reverse('manufacturer-detail', kwargs={'pk': obj.id})
return settings.SERVER_URL + url
admin.site.register(Manufacturer, ManufacturerAdmin)

5
inventory/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class InventoryConfig(AppConfig):
name = 'inventory'

View file

@ -0,0 +1,128 @@
# Generated by Django 3.0.4 on 2020-08-03 21:20
import django.contrib.postgres.fields
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Container',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
migrations.CreateModel(
name='Distributor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('description', models.CharField(max_length=4096)),
('created_at', models.DateTimeField(auto_now_add=True)),
('changed_at', models.DateTimeField(auto_now=True)),
('web_link', models.URLField(blank=True, null=True)),
('search_link', models.URLField(blank=True, null=True, verbose_name='Use {} for search placeholder')),
('phone', models.CharField(blank=True, max_length=128, null=True)),
],
),
migrations.CreateModel(
name='Layout',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('description', models.CharField(max_length=4096)),
('created_at', models.DateTimeField(auto_now_add=True)),
('changed_at', models.DateTimeField(auto_now=True)),
('data', django.contrib.postgres.fields.jsonb.JSONField()),
],
),
migrations.CreateModel(
name='Manufacturer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('description', models.CharField(max_length=4096)),
('created_at', models.DateTimeField(auto_now_add=True)),
('changed_at', models.DateTimeField(auto_now=True)),
('web_link', models.URLField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='Workshop',
fields=[
('container_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='inventory.Container')),
('name', models.CharField(max_length=255, unique=True)),
('description', models.CharField(max_length=4096)),
('created_at', models.DateTimeField(auto_now_add=True)),
('changed_at', models.DateTimeField(auto_now=True)),
],
bases=('inventory.container',),
),
migrations.CreateModel(
name='Item',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('index', models.PositiveIntegerField(verbose_name='Index of compartment in layout')),
('name', models.CharField(max_length=255, unique=True)),
('description', models.CharField(max_length=4096)),
('created_at', models.DateTimeField(auto_now_add=True)),
('changed_at', models.DateTimeField(auto_now=True)),
('metadata', django.contrib.postgres.fields.jsonb.JSONField(verbose_name='Custom metadata, used by templates')),
('distributor_item_no', models.CharField(blank=True, max_length=255, null=True)),
('price', models.DecimalField(blank=True, decimal_places=3, max_digits=7, null=True)),
('last_ordered_on', models.DateField(blank=True, null=True)),
('documentation', django.contrib.postgres.fields.ArrayField(base_field=models.FileField(upload_to=''), size=None)),
('container', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='item_related', to='inventory.Container')),
('distributor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='inventory.Distributor')),
('manufacturer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='inventory.Manufacturer')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='container',
name='layout',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='inventory.Layout'),
),
migrations.CreateModel(
name='Box',
fields=[
('container_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='inventory.Container')),
('index', models.PositiveIntegerField(verbose_name='Index of compartment in layout')),
('name', models.CharField(max_length=255, unique=True)),
('description', models.CharField(max_length=4096)),
('created_at', models.DateTimeField(auto_now_add=True)),
('changed_at', models.DateTimeField(auto_now=True)),
('container', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='box_related', to='inventory.Container')),
],
options={
'abstract': False,
},
bases=('inventory.container', models.Model),
),
migrations.CreateModel(
name='Area',
fields=[
('container_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='inventory.Container')),
('index', models.PositiveIntegerField(verbose_name='Index of compartment in layout')),
('name', models.CharField(max_length=255, unique=True)),
('description', models.CharField(max_length=4096)),
('created_at', models.DateTimeField(auto_now_add=True)),
('changed_at', models.DateTimeField(auto_now=True)),
('container', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='area_related', to='inventory.Container')),
],
options={
'abstract': False,
},
bases=('inventory.container', models.Model),
),
]

View file

@ -0,0 +1,43 @@
# Generated by Django 3.0.4 on 2020-08-03 21:40
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='box',
options={'verbose_name_plural': 'Boxes'},
),
migrations.AddField(
model_name='distributor',
name='email',
field=models.EmailField(blank=True, default=None, max_length=254, null=True),
),
migrations.AlterField(
model_name='area',
name='container',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='area_related', to='inventory.Container'),
),
migrations.AlterField(
model_name='box',
name='container',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='box_related', to='inventory.Container'),
),
migrations.AlterField(
model_name='distributor',
name='search_link',
field=models.URLField(blank=True, help_text='Use {} for search placeholder', null=True),
),
migrations.AlterField(
model_name='item',
name='container',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='item_related', to='inventory.Container'),
),
]

View file

@ -0,0 +1,39 @@
# Generated by Django 3.0.4 on 2020-08-03 22:30
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0002_auto_20200803_2340'),
]
operations = [
migrations.RemoveField(
model_name='item',
name='documentation',
),
migrations.AlterField(
model_name='item',
name='metadata',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='Custom metadata, used by templates'),
),
migrations.AlterField(
model_name='manufacturer',
name='description',
field=models.CharField(blank=True, max_length=4096),
),
migrations.CreateModel(
name='Documentation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('changed_at', models.DateTimeField(auto_now=True)),
('file', models.FileField(upload_to='')),
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.Item')),
],
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 3.0.4 on 2020-08-03 22:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_auto_20200804_0030'),
]
operations = [
migrations.AddField(
model_name='distributor',
name='icon',
field=models.ImageField(blank=True, null=True, upload_to=''),
),
migrations.AddField(
model_name='manufacturer',
name='icon',
field=models.ImageField(blank=True, null=True, upload_to=''),
),
]

View file

View file

@ -0,0 +1,9 @@
from .area import Area
from .box import Box
from .distributor import Distributor
from .item import Item
from .layout import Layout
from .manufacturer import Manufacturer
from .workshop import Workshop
from .container import Container, CanBeContained
from .documentation import Documentation

9
inventory/models/area.py Normal file
View file

@ -0,0 +1,9 @@
from django.db import models
from .container import Container, CanBeContained
class Area(CanBeContained, Container):
name = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=4096)
created_at = models.DateTimeField(auto_now_add=True)
changed_at = models.DateTimeField(auto_now=True)

12
inventory/models/box.py Normal file
View file

@ -0,0 +1,12 @@
from django.db import models
from .container import CanBeContained, Container
class Box(CanBeContained, Container):
name = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=4096)
created_at = models.DateTimeField(auto_now_add=True)
changed_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = 'Boxes'

View file

@ -0,0 +1,35 @@
from django.db import models
from django.apps import apps
class Container(models.Model):
layout = models.ForeignKey('inventory.Layout', on_delete=models.PROTECT, null=True, blank=True)
def _subclass(self):
for model in apps.get_app_config('inventory').get_models():
if model == Container:
continue
if issubclass(model, Container):
q = model.objects.filter(container_ptr_id=self.id)
if q.exists():
return model, q.first()
return Container, None
def __str__(self):
subclass, obj = self._subclass()
return '{}: {}'.format(subclass.__name__, obj.name)
@property
def display_name(self):
_, obj = self._subclass()
if obj is not None:
return obj.name
return 'Container'
class CanBeContained(models.Model):
container = models.ForeignKey('inventory.Container', related_name="%(class)s_related", null=True, on_delete=models.CASCADE)
index = models.PositiveIntegerField('Index of compartment in layout')
class Meta:
abstract = True

View file

@ -0,0 +1,17 @@
from django.db import models
class Distributor(models.Model):
name = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=4096)
created_at = models.DateTimeField(auto_now_add=True)
changed_at = models.DateTimeField(auto_now=True)
web_link = models.URLField(null=True, blank=True)
search_link = models.URLField(help_text='Use {} for search placeholder', null=True, blank=True)
phone = models.CharField(max_length=128, null=True, blank=True)
email = models.EmailField(null=True, blank=True, default=None)
icon = models.ImageField(null=True, blank=True)
def __str__(self):
return self.name

View file

@ -0,0 +1,9 @@
from django.db import models
class Documentation(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
changed_at = models.DateTimeField(auto_now=True)
file = models.FileField()
item = models.ForeignKey('inventory.Item', on_delete=models.CASCADE, related_name='documentation')

22
inventory/models/item.py Normal file
View file

@ -0,0 +1,22 @@
from django.db import models
from django.contrib.postgres.fields import JSONField, ArrayField
from .container import CanBeContained
class Item(CanBeContained):
name = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=4096)
created_at = models.DateTimeField(auto_now_add=True)
changed_at = models.DateTimeField(auto_now=True)
metadata = JSONField('Custom metadata, used by templates', blank=True, null=True)
manufacturer = models.ForeignKey('inventory.Manufacturer', null=True, blank=True, on_delete=models.PROTECT)
distributor = models.ForeignKey('inventory.Distributor', null=True, blank=True, on_delete=models.PROTECT)
distributor_item_no = models.CharField(max_length=255, null=True, blank=True)
price = models.DecimalField(decimal_places=3, max_digits=7, null=True, blank=True)
last_ordered_on = models.DateField(null=True, blank=True)
def __str__(self):
return self.name

View file

@ -0,0 +1,13 @@
from django.db import models
from django.contrib.postgres.fields import JSONField
class Layout(models.Model):
name = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=4096)
created_at = models.DateTimeField(auto_now_add=True)
changed_at = models.DateTimeField(auto_now=True)
data = JSONField()
def __str__(self):
return self.name

View file

@ -0,0 +1,14 @@
from django.db import models
class Manufacturer(models.Model):
name = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=4096, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
changed_at = models.DateTimeField(auto_now=True)
web_link = models.URLField(null=True, blank=True)
icon = models.ImageField(null=True, blank=True)
def __str__(self):
return self.name

View file

@ -0,0 +1,9 @@
from django.db import models
from .container import Container
class Workshop(Container):
name = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=4096)
created_at = models.DateTimeField(auto_now_add=True)
changed_at = models.DateTimeField(auto_now=True)

View file

@ -0,0 +1,16 @@
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" content="text/css" href="{% static 'main.css' %}">
{% block header %}{% endblock %}
</head>
<body>
<main>
{% block content %}
{% endblock %}
</main>
</body>
</html>

View file

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}{{ area.container.display_name }} - {{ area.name }}{% endblock %}
{% block content %}
<h2>{{ area.container.display_name }} - {{ area.name }}</h2>
{% if area.area_related.exists %}
<h3>Areas</h3>
<ul>
{% for a in area.area_related.all %}
<li><a href="{% url 'area-detail' a.id %}" title="{{ a.description }}">{{ a.name }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% if area.box_related.exists %}
<h3>Boxes</h3>
<ul>
{% for box in area.box_related.all %}
<li><a href="{% url 'box-detail' box.id %}" title="{{ box.description }}">{{ box.name }} ({{ box.layout.name }})</a></li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}{{ box.container.display_name }} - {{ box.name }}{% endblock %}
{% block content %}
<h2>{{ box.container.display_name }} - {{ box.name }}</h2>
{% if box.box_related.exists %}
<h3>Boxes</h3>
<ul>
{% for b in box.box_related.all %}
<li><a href="{% url 'box-detail' b.id %}" title="{{ b.description }}">{{ b.name }} ({{ b.layout.name }})</a></li>
{% endfor %}
</ul>
{% endif %}
{% if box.item_related.exists %}
<h3>Items</h3>
<ul>
{% for item in box.item_related.all %}
<li><a href="{% url 'item-detail' item.id %}" title="{{ item.description }}">{{ item.name }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% load formatstring %}
{% block title %}{{ item.name }}{% endblock %}
{% block content %}
<h2>{{ item.name }}</h2>
<p>{{ item.description }}</p>
<ul>
{% if item.manufacturer %}
<li>Manufacturer: <a href="{% url 'manufacturer-detail' item.manufacturer.id %}">{% if item.manufacturer.icon %}<img src="{{ item.manufacturer.icon.url }}" class="icon">{% endif %}{{ item.manufacturer.name }}</a></li>
{% endif %}
{% if item.distributor %}
<li>Distributor: <a href="{% url 'distributor-detail' item.distributor.id %}">{% if item.distributor.icon %}<img src="{{ item.distributor.icon.url }}" class="icon">{% endif %}{{ item.distributor.name }}</a></li>
{% endif %}
{% if item.price %}
<li>Price: {{ item.price }} Euro</li>
{% endif %}
{% if item.last_ordered_on %}
<li>Last ordered: {{ item.last_ordered_on }}</li>
{% endif %}
{% if item.distributor_item_no %}
<li>Link: <a href="{% formatstring item.distributor.search_link item.distributor_item_no %}">{% formatstring item.distributor.search_link item.distributor_item_no %}</a></li>
{% endif %}
<li>Created at: {{ item.created_at }}</li>
<li>Last change: {{ item.changed_at }}</li>
</ul>
{% if item.documentation.exists %}
<h3>Datasheets</h3>
<ul>
{% for doc in item.documentation.all %}
<li><a href="{{ doc.file.url }}">{{ doc.file.name }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}Workshop {{ workshop.name }}{% endblock %}
{% block content %}
<h2>{{ workshop.name }}</h2>
{% if workshop.area_related.exists %}
<h3>Areas</h3>
<ul>
{% for area in workshop.area_related.all %}
<li><a href="{% url 'area-detail' area.id %}" title="{{ area.description }}">{{ area.name }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% if workshop.box_related.exists %}
<h3>Boxes</h3>
<ul>
{% for box in workshop.box_related.all %}
<li><a href="{% url 'box-detail' box.id %}" title="{{ box.description }}">{{ box.name }} ({{ box.layout.name }})</a></li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}Workshops{% endblock %}
{% block content %}
<h2>Workshops</h2>
<ul>
{% for workshop in object_list %}
<li><a href="{% url 'workshop-detail' workshop.id %}" title="{{ workshop.description }}">{{ workshop.name }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View file

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% block content %}
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if next %}
{% if user.is_authenticated %}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p>Please login to see this page.</p>
{% endif %}
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.password.label_tag }}</td>
<td>{{ form.password }}</td>
</tr>
</table>
<input type="submit" value="login">
<input type="hidden" name="next" value="{{ next }}">
</form>
{# Assumes you setup the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
{% endblock %}

View file

View file

@ -0,0 +1,7 @@
from django import template
register = template.Library()
@register.simple_tag(name='formatstring')
def formatstring(value, *args):
return value.format(*args)

3
inventory/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

36
inventory/urls.py Normal file
View file

@ -0,0 +1,36 @@
"""inventory URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from .views import WorkshopListView, AreaListView, BoxListView, ItemListView, ManufacturerListView, DistributorListView
from .views import WorkshopView, AreaView, BoxView, ItemView, DistributorView, ManufacturerView
urlpatterns = [
path('workshops', WorkshopListView.as_view(), name='workshop-list'),
path('workshop/<int:pk>/areas', AreaListView.as_view(), name='area-list'),
path('workshop/<int:pk>', WorkshopView.as_view(), name='workshop-detail'),
path('area/<int:pk>/boxes', BoxListView.as_view(), name='box-list'),
path('area/<int:pk>', AreaView.as_view(), name='area-detail'),
path('box/<int:pk>/items', ItemListView.as_view(), name='item-list'),
path('box/<int:pk>/boxes', BoxListView.as_view(), name='box-recurse-list'),
path('box/<int:pk>', BoxView.as_view(), name='box-detail'),
path('item/<int:pk>', ItemView.as_view(), name='item-detail'),
path('manufacturers', ManufacturerListView.as_view(), name='manufacturer-list'),
path('manufacturer/<int:pk>', ManufacturerView.as_view(), name='manufacturer-detail'),
path('distributors', DistributorListView.as_view(), name='distributor-list'),
path('distributor/<int:pk>', DistributorView.as_view(), name='distributor-detail'),
]

View file

@ -0,0 +1,6 @@
from .area import AreaView, AreaListView
from .box import BoxView, BoxListView
from .distributor import DistributorView, DistributorListView
from .item import ItemView, ItemListView
from .manufacturer import ManufacturerView, ManufacturerListView
from .workshop import WorkshopView, WorkshopListView

17
inventory/views/area.py Normal file
View file

@ -0,0 +1,17 @@
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView
from inventory.models import Area
@method_decorator(login_required, name='dispatch')
class AreaView(DetailView):
context_object_name = 'area'
queryset = Area.objects.all().select_related('container', 'layout').prefetch_related('area_related', 'box_related')
@method_decorator(login_required, name='dispatch')
class AreaListView(ListView):
model = Area

17
inventory/views/box.py Normal file
View file

@ -0,0 +1,17 @@
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView
from inventory.models import Box
@method_decorator(login_required, name='dispatch')
class BoxView(DetailView):
context_object_name = 'box'
queryset = Box.objects.all().select_related('container', 'layout').prefetch_related('box_related')
@method_decorator(login_required, name='dispatch')
class BoxListView(ListView):
model = Box

View file

@ -0,0 +1,9 @@
from django.views import View
class DistributorView(View):
pass
class DistributorListView(View):
pass

16
inventory/views/item.py Normal file
View file

@ -0,0 +1,16 @@
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView
from inventory.models import Item
@method_decorator(login_required, name='dispatch')
class ItemView(DetailView):
context_object_name = 'item'
queryset = Item.objects.all().select_related('container', 'container__layout', 'manufacturer', 'distributor').prefetch_related('documentation')
@method_decorator(login_required, name='dispatch')
class ItemListView(ListView):
model = Item

View file

@ -0,0 +1,9 @@
from django.views import View
class ManufacturerView(View):
pass
class ManufacturerListView(View):
pass

View file

@ -0,0 +1,17 @@
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView
from inventory.models import Workshop
@method_decorator(login_required, name='dispatch')
class WorkshopView(DetailView):
context_object_name = 'workshop'
queryset = Workshop.objects.all().prefetch_related('area_related', 'box_related')
@method_decorator(login_required, name='dispatch')
class WorkshopListView(ListView):
model = Workshop

View file

18
inventory_project/asgi.py Normal file
View file

@ -0,0 +1,18 @@
"""
ASGI config for inventory_project project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""
import os
from channels.routing import ProtocolTypeRouter
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'inventory_project.settings')
application = ProtocolTypeRouter({
# (http->django views is added by default)
})

View file

@ -0,0 +1,136 @@
"""
Django settings for inventory_project project.
Generated by 'django-admin startproject' using Django 3.0.4.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
from typing import List
import os
import sys
import asyncio
if sys.platform == 'win32':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Externally visible URL of the server
SERVER_URL = "http://127.0.0.1:8000"
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'nqo*a(^g$8#0%&+*_7#b_7ybn-znk4#=45_(qy-lq-^v675pqk'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS: List[str] = []
# Application definition
INSTALLED_APPS = [
'inventory',
'nested_admin',
'whitenoise.runserver_nostatic',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'inventory_project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'inventory_project.wsgi.application'
ASGI_APPLICATION = 'inventory_project.asgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'inventory',
'USER': 'inventory',
'PASSWORD': 'inventory'
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'static')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

23
inventory_project/urls.py Normal file
View file

@ -0,0 +1,23 @@
"""inventory_project URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include('inventory.urls')),
path('accounts/', include('django.contrib.auth.urls')),
path('admin/', admin.site.urls),
]

16
inventory_project/wsgi.py Normal file
View file

@ -0,0 +1,16 @@
"""
WSGI config for inventory_project project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'inventory_project.settings')
application = get_wsgi_application()

21
manage.py Normal file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'inventory_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

490
poetry.lock generated Normal file
View file

@ -0,0 +1,490 @@
[[package]]
category = "main"
description = "ASGI specs, helper code, and adapters"
name = "asgiref"
optional = false
python-versions = ">=3.5"
version = "3.2.7"
[package.extras]
tests = ["pytest (>=4.3.0,<4.4.0)", "pytest-asyncio (>=0.10.0,<0.11.0)"]
[[package]]
category = "dev"
description = "An abstract syntax tree for Python with inference support."
name = "astroid"
optional = false
python-versions = ">=3.5.*"
version = "2.3.3"
[package.dependencies]
lazy-object-proxy = ">=1.4.0,<1.5.0"
six = ">=1.12,<2.0"
wrapt = ">=1.11.0,<1.12.0"
[[package]]
category = "dev"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\""
name = "colorama"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.4.3"
[[package]]
category = "main"
description = "Add Python and JavaScript style comments in your JSON files."
name = "commentjson"
optional = false
python-versions = "*"
version = "0.8.3"
[package.dependencies]
lark-parser = ">=0.7.1,<0.8.0"
[[package]]
category = "main"
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
name = "django"
optional = false
python-versions = ">=3.6"
version = "3.0.4"
[package.dependencies]
asgiref = ">=3.2,<4.0"
pytz = "*"
sqlparse = ">=0.2.2"
[package.extras]
argon2 = ["argon2-cffi (>=16.1.0)"]
bcrypt = ["bcrypt"]
[[package]]
category = "main"
description = "Django admin classes that allow for nested inlines"
name = "django-nested-admin"
optional = false
python-versions = "*"
version = "3.3.2"
[package.dependencies]
python-monkey-business = ">=1.0.0"
six = "*"
[[package]]
category = "dev"
description = "Discover and load entry points from installed packages."
name = "entrypoints"
optional = false
python-versions = ">=2.7"
version = "0.3"
[[package]]
category = "dev"
description = "the modular source code checker: pep8, pyflakes and co"
name = "flake8"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.7.9"
[package.dependencies]
entrypoints = ">=0.3.0,<0.4.0"
mccabe = ">=0.6.0,<0.7.0"
pycodestyle = ">=2.5.0,<2.6.0"
pyflakes = ">=2.1.0,<2.2.0"
[[package]]
category = "main"
description = "WSGI HTTP Server for UNIX"
name = "gunicorn"
optional = false
python-versions = ">=3.4"
version = "20.0.4"
[package.dependencies]
setuptools = ">=3.0"
[package.extras]
eventlet = ["eventlet (>=0.9.7)"]
gevent = ["gevent (>=0.13)"]
setproctitle = ["setproctitle"]
tornado = ["tornado (>=0.2)"]
[[package]]
category = "dev"
description = "A Python utility / library to sort Python imports."
name = "isort"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "4.3.21"
[package.extras]
pipfile = ["pipreqs", "requirementslib"]
pyproject = ["toml"]
requirements = ["pipreqs", "pip-api"]
xdg_home = ["appdirs (>=1.4.0)"]
[[package]]
category = "main"
description = "a modern parsing library"
name = "lark-parser"
optional = false
python-versions = "*"
version = "0.7.8"
[[package]]
category = "dev"
description = "A fast and thorough lazy object proxy."
name = "lazy-object-proxy"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.4.3"
[[package]]
category = "dev"
description = "McCabe checker, plugin for flake8"
name = "mccabe"
optional = false
python-versions = "*"
version = "0.6.1"
[[package]]
category = "dev"
description = "Optional static typing for Python"
name = "mypy"
optional = false
python-versions = ">=3.5"
version = "0.770"
[package.dependencies]
mypy-extensions = ">=0.4.3,<0.5.0"
typed-ast = ">=1.4.0,<1.5.0"
typing-extensions = ">=3.7.4"
[package.extras]
dmypy = ["psutil (>=4.0)"]
[[package]]
category = "dev"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
name = "mypy-extensions"
optional = false
python-versions = "*"
version = "0.4.3"
[[package]]
category = "main"
description = "Python Imaging Library (Fork)"
name = "pillow"
optional = false
python-versions = ">=3.5"
version = "7.2.0"
[[package]]
category = "main"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
name = "psycopg2"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "2.8.4"
[[package]]
category = "dev"
description = "Python style guide checker"
name = "pycodestyle"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.5.0"
[[package]]
category = "dev"
description = "passive checker of Python programs"
name = "pyflakes"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.1.1"
[[package]]
category = "dev"
description = "python code static checker"
name = "pylint"
optional = false
python-versions = ">=3.5.*"
version = "2.4.4"
[package.dependencies]
astroid = ">=2.3.0,<2.4"
colorama = "*"
isort = ">=4.2.5,<5"
mccabe = ">=0.6,<0.7"
[[package]]
category = "main"
description = "Utility functions for monkey-patching python code"
name = "python-monkey-business"
optional = false
python-versions = "*"
version = "1.0.0"
[package.dependencies]
six = ">=1.7.0"
[[package]]
category = "main"
description = "World timezone definitions, modern and historical"
name = "pytz"
optional = false
python-versions = "*"
version = "2019.3"
[[package]]
category = "main"
description = "Python 2 and 3 compatibility utilities"
name = "six"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.14.0"
[[package]]
category = "main"
description = "Non-validating SQL parser"
name = "sqlparse"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.3.1"
[[package]]
category = "dev"
description = "a fork of Python 2 and 3 ast modules with type comment support"
name = "typed-ast"
optional = false
python-versions = "*"
version = "1.4.1"
[[package]]
category = "dev"
description = "Backported and Experimental Type Hints for Python 3.5+"
name = "typing-extensions"
optional = false
python-versions = "*"
version = "3.7.4.1"
[[package]]
category = "main"
description = "Radically simplified static file serving for WSGI applications"
name = "whitenoise"
optional = false
python-versions = ">=3.5, <4"
version = "5.0.1"
[package.extras]
brotli = ["brotli"]
[[package]]
category = "dev"
description = "Module for decorators, wrappers and monkey patching."
name = "wrapt"
optional = false
python-versions = "*"
version = "1.11.2"
[metadata]
content-hash = "f67f4bd254b8311158851ce498b2cabb1ac21093917f49fed0ee6449420a59bf"
python-versions = "^3.8"
[metadata.files]
asgiref = [
{file = "asgiref-3.2.7-py2.py3-none-any.whl", hash = "sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"},
{file = "asgiref-3.2.7.tar.gz", hash = "sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5"},
]
astroid = [
{file = "astroid-2.3.3-py3-none-any.whl", hash = "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"},
{file = "astroid-2.3.3.tar.gz", hash = "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a"},
]
colorama = [
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
]
commentjson = [
{file = "commentjson-0.8.3.tar.gz", hash = "sha256:6f8703519fe41e4157903ce0ff712f9c16ef1d1d3aee2eb42645c0155aa2a652"},
]
django = [
{file = "Django-3.0.4-py3-none-any.whl", hash = "sha256:89e451bfbb815280b137e33e454ddd56481fdaa6334054e6e031041ee1eda360"},
{file = "Django-3.0.4.tar.gz", hash = "sha256:50b781f6cbeb98f673aa76ed8e572a019a45e52bdd4ad09001072dfd91ab07c8"},
]
django-nested-admin = [
{file = "django-nested-admin-3.3.2.tar.gz", hash = "sha256:7ec58af67107e6d7b2d0fd4fe03629eff8f7957c4aef5033c4700da1bd2d7870"},
{file = "django_nested_admin-3.3.2-py2.py3-none-any.whl", hash = "sha256:2e6706050057f5dc5664bc90d034d4ed9e3ae032ead6b063da9982c9e45ab7f3"},
]
entrypoints = [
{file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
{file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
]
flake8 = [
{file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"},
{file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"},
]
gunicorn = [
{file = "gunicorn-20.0.4-py2.py3-none-any.whl", hash = "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"},
{file = "gunicorn-20.0.4.tar.gz", hash = "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626"},
]
isort = [
{file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
{file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"},
]
lark-parser = [
{file = "lark-parser-0.7.8.tar.gz", hash = "sha256:26215ebb157e6fb2ee74319aa4445b9f3b7e456e26be215ce19fdaaa901c20a4"},
]
lazy-object-proxy = [
{file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"},
{file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"},
{file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"},
{file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"},
{file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"},
{file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"},
{file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"},
{file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"},
{file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"},
{file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"},
{file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"},
{file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"},
{file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"},
{file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"},
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"},
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"},
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"},
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"},
{file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"},
{file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"},
{file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"},
]
mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
mypy = [
{file = "mypy-0.770-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600"},
{file = "mypy-0.770-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:86c857510a9b7c3104cf4cde1568f4921762c8f9842e987bc03ed4f160925754"},
{file = "mypy-0.770-cp35-cp35m-win_amd64.whl", hash = "sha256:a8ffcd53cb5dfc131850851cc09f1c44689c2812d0beb954d8138d4f5fc17f65"},
{file = "mypy-0.770-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:7687f6455ec3ed7649d1ae574136835a4272b65b3ddcf01ab8704ac65616c5ce"},
{file = "mypy-0.770-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3beff56b453b6ef94ecb2996bea101a08f1f8a9771d3cbf4988a61e4d9973761"},
{file = "mypy-0.770-cp36-cp36m-win_amd64.whl", hash = "sha256:15b948e1302682e3682f11f50208b726a246ab4e6c1b39f9264a8796bb416aa2"},
{file = "mypy-0.770-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:b90928f2d9eb2f33162405f32dde9f6dcead63a0971ca8a1b50eb4ca3e35ceb8"},
{file = "mypy-0.770-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c56ffe22faa2e51054c5f7a3bc70a370939c2ed4de308c690e7949230c995913"},
{file = "mypy-0.770-cp37-cp37m-win_amd64.whl", hash = "sha256:8dfb69fbf9f3aeed18afffb15e319ca7f8da9642336348ddd6cab2713ddcf8f9"},
{file = "mypy-0.770-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:219a3116ecd015f8dca7b5d2c366c973509dfb9a8fc97ef044a36e3da66144a1"},
{file = "mypy-0.770-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7ec45a70d40ede1ec7ad7f95b3c94c9cf4c186a32f6bacb1795b60abd2f9ef27"},
{file = "mypy-0.770-cp38-cp38-win_amd64.whl", hash = "sha256:f91c7ae919bbc3f96cd5e5b2e786b2b108343d1d7972ea130f7de27fdd547cf3"},
{file = "mypy-0.770-py3-none-any.whl", hash = "sha256:3b1fc683fb204c6b4403a1ef23f0b1fac8e4477091585e0c8c54cbdf7d7bb164"},
{file = "mypy-0.770.tar.gz", hash = "sha256:8a627507ef9b307b46a1fea9513d5c98680ba09591253082b4c48697ba05a4ae"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
pillow = [
{file = "Pillow-7.2.0-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:1ca594126d3c4def54babee699c055a913efb01e106c309fa6b04405d474d5ae"},
{file = "Pillow-7.2.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c92302a33138409e8f1ad16731568c55c9053eee71bb05b6b744067e1b62380f"},
{file = "Pillow-7.2.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8dad18b69f710bf3a001d2bf3afab7c432785d94fcf819c16b5207b1cfd17d38"},
{file = "Pillow-7.2.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:431b15cffbf949e89df2f7b48528be18b78bfa5177cb3036284a5508159492b5"},
{file = "Pillow-7.2.0-cp35-cp35m-win32.whl", hash = "sha256:09d7f9e64289cb40c2c8d7ad674b2ed6105f55dc3b09aa8e4918e20a0311e7ad"},
{file = "Pillow-7.2.0-cp35-cp35m-win_amd64.whl", hash = "sha256:0295442429645fa16d05bd567ef5cff178482439c9aad0411d3f0ce9b88b3a6f"},
{file = "Pillow-7.2.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:ec29604081f10f16a7aea809ad42e27764188fc258b02259a03a8ff7ded3808d"},
{file = "Pillow-7.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:612cfda94e9c8346f239bf1a4b082fdd5c8143cf82d685ba2dba76e7adeeb233"},
{file = "Pillow-7.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0a80dd307a5d8440b0a08bd7b81617e04d870e40a3e46a32d9c246e54705e86f"},
{file = "Pillow-7.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:06aba4169e78c439d528fdeb34762c3b61a70813527a2c57f0540541e9f433a8"},
{file = "Pillow-7.2.0-cp36-cp36m-win32.whl", hash = "sha256:f7e30c27477dffc3e85c2463b3e649f751789e0f6c8456099eea7ddd53be4a8a"},
{file = "Pillow-7.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:ffe538682dc19cc542ae7c3e504fdf54ca7f86fb8a135e59dd6bc8627eae6cce"},
{file = "Pillow-7.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:94cf49723928eb6070a892cb39d6c156f7b5a2db4e8971cb958f7b6b104fb4c4"},
{file = "Pillow-7.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6edb5446f44d901e8683ffb25ebdfc26988ee813da3bf91e12252b57ac163727"},
{file = "Pillow-7.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:52125833b070791fcb5710fabc640fc1df07d087fc0c0f02d3661f76c23c5b8b"},
{file = "Pillow-7.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:9ad7f865eebde135d526bb3163d0b23ffff365cf87e767c649550964ad72785d"},
{file = "Pillow-7.2.0-cp37-cp37m-win32.whl", hash = "sha256:c79f9c5fb846285f943aafeafda3358992d64f0ef58566e23484132ecd8d7d63"},
{file = "Pillow-7.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d350f0f2c2421e65fbc62690f26b59b0bcda1b614beb318c81e38647e0f673a1"},
{file = "Pillow-7.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:6d7741e65835716ceea0fd13a7d0192961212fd59e741a46bbed7a473c634ed6"},
{file = "Pillow-7.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:edf31f1150778abd4322444c393ab9c7bd2af271dd4dafb4208fb613b1f3cdc9"},
{file = "Pillow-7.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:d08b23fdb388c0715990cbc06866db554e1822c4bdcf6d4166cf30ac82df8c41"},
{file = "Pillow-7.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5e51ee2b8114def244384eda1c82b10e307ad9778dac5c83fb0943775a653cd8"},
{file = "Pillow-7.2.0-cp38-cp38-win32.whl", hash = "sha256:725aa6cfc66ce2857d585f06e9519a1cc0ef6d13f186ff3447ab6dff0a09bc7f"},
{file = "Pillow-7.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:a060cf8aa332052df2158e5a119303965be92c3da6f2d93b6878f0ebca80b2f6"},
{file = "Pillow-7.2.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:25930fadde8019f374400f7986e8404c8b781ce519da27792cbe46eabec00c4d"},
{file = "Pillow-7.2.0.tar.gz", hash = "sha256:97f9e7953a77d5a70f49b9a48da7776dc51e9b738151b22dacf101641594a626"},
]
psycopg2 = [
{file = "psycopg2-2.8.4-cp27-cp27m-win32.whl", hash = "sha256:72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b"},
{file = "psycopg2-2.8.4-cp27-cp27m-win_amd64.whl", hash = "sha256:893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0"},
{file = "psycopg2-2.8.4-cp34-cp34m-win32.whl", hash = "sha256:9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38"},
{file = "psycopg2-2.8.4-cp34-cp34m-win_amd64.whl", hash = "sha256:b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6"},
{file = "psycopg2-2.8.4-cp35-cp35m-win32.whl", hash = "sha256:ef6df7e14698e79c59c7ee7cf94cd62e5b869db369ed4b1b8f7b729ea825712a"},
{file = "psycopg2-2.8.4-cp35-cp35m-win_amd64.whl", hash = "sha256:965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4"},
{file = "psycopg2-2.8.4-cp36-cp36m-win32.whl", hash = "sha256:ed686e5926929887e2c7ae0a700e32c6129abb798b4ad2b846e933de21508151"},
{file = "psycopg2-2.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:dca2d7203f0dfce8ea4b3efd668f8ea65cd2b35112638e488a4c12594015f67b"},
{file = "psycopg2-2.8.4-cp37-cp37m-win32.whl", hash = "sha256:8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c"},
{file = "psycopg2-2.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d"},
{file = "psycopg2-2.8.4-cp38-cp38-win32.whl", hash = "sha256:4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677"},
{file = "psycopg2-2.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f"},
{file = "psycopg2-2.8.4.tar.gz", hash = "sha256:f898e5cc0a662a9e12bde6f931263a1bbd350cfb18e1d5336a12927851825bb6"},
]
pycodestyle = [
{file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"},
{file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"},
]
pyflakes = [
{file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"},
{file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"},
]
pylint = [
{file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"},
{file = "pylint-2.4.4.tar.gz", hash = "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd"},
]
python-monkey-business = [
{file = "python-monkey-business-1.0.0.tar.gz", hash = "sha256:9976522989766f00b2aaa24ec96eacb91a6de7b7001d1452079323b071988e0e"},
{file = "python_monkey_business-1.0.0-py2.py3-none-any.whl", hash = "sha256:6d4cf47f011945db838ccf04643acd49b82f7ad6ab7ecba4c8165385687a828a"},
]
pytz = [
{file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"},
{file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"},
]
six = [
{file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
{file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
]
sqlparse = [
{file = "sqlparse-0.3.1-py2.py3-none-any.whl", hash = "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e"},
{file = "sqlparse-0.3.1.tar.gz", hash = "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"},
]
typed-ast = [
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
{file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
{file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
{file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
{file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
{file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
{file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
{file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
{file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
{file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
{file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
{file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
]
typing-extensions = [
{file = "typing_extensions-3.7.4.1-py2-none-any.whl", hash = "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d"},
{file = "typing_extensions-3.7.4.1-py3-none-any.whl", hash = "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"},
{file = "typing_extensions-3.7.4.1.tar.gz", hash = "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2"},
]
whitenoise = [
{file = "whitenoise-5.0.1-py2.py3-none-any.whl", hash = "sha256:62556265ec1011bd87113fb81b7516f52688887b7a010ee899ff1fd18fd22700"},
{file = "whitenoise-5.0.1.tar.gz", hash = "sha256:0f9137f74bd95fa54329ace88d8dc695fbe895369a632e35f7a136e003e41d73"},
]
wrapt = [
{file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"},
]

24
pyproject.toml Normal file
View file

@ -0,0 +1,24 @@
[tool.poetry]
name = "inventory"
version = "0.1.0"
description = ""
authors = ["Johannes Schriewer <hallo@dunkelstern.de>"]
[tool.poetry.dependencies]
python = "^3.8"
django = "^3.0.4"
gunicorn = "^20.0.4"
whitenoise = "^5.0.1"
commentjson = "^0.8.3"
psycopg2 = "^2.8.4"
django-nested-admin = "^3.3.2"
pillow = "^7.2.0"
[tool.poetry.dev-dependencies]
mypy = "^0.770"
flake8 = "^3.7.9"
pylint = "^2.4.4"
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"