Merge branch 'master' into patch-1
This commit is contained in:
commit
b7e1390fac
39
README.md
39
README.md
|
@ -4,6 +4,18 @@ Pantry inventory management
|
|||
# Inventory management system written in django - python
|
||||
The object of this django app is to keep track of which goods you own, when they will expire, when you should consume them and when you shoud buy more.
|
||||
|
||||
## features
|
||||
- Enter items in your pantry (or other locations, with their location) with their expiry date
|
||||
- Items can have a minimum quantity you always want to keep in the pantry
|
||||
- a shopping list is automatically populated with items whose quantity is below this minimum quantity
|
||||
- you can add one off items to this shopping list that are not tracked in the inventory.
|
||||
- You get an overview of items per expiry date so you know what to consume first (or throw away)
|
||||
|
||||
- Extensive search and filtering and grouping on locations, categories, units, expiry date using the auto generated django admin
|
||||
interface
|
||||
- Locations are recursive so things can be in the kitchen pantry closet on the 3rd shelf on the right and still be in
|
||||
the kitchen
|
||||
|
||||
# Getting started
|
||||
`pip3 install django`
|
||||
|
||||
|
@ -29,18 +41,39 @@ The object of this django app is to keep track of which goods you own, when they
|
|||
# feature requests
|
||||
## High
|
||||
- make expirations a calendar view, generate ics file to import
|
||||
- add locations to items
|
||||
- public wishlist
|
||||
- one off shoping list
|
||||
- easy clearing of shopping list
|
||||
- add easy consume view
|
||||
- save date consumed in history
|
||||
|
||||
## medium
|
||||
# medium high
|
||||
- mobile app
|
||||
- (requires rest api?)
|
||||
|
||||
# medium
|
||||
- make work offline
|
||||
- add users
|
||||
- federated? use sqrl instead of passwords?
|
||||
|
||||
# medium low
|
||||
- allow for lending stuff out
|
||||
- auto mailings when things should be returned?
|
||||
- double bookkeeping of where things are if both parties have this set up
|
||||
- ipfs (+ blockchain?)
|
||||
- will be done in a seperate app, oos for inventory
|
||||
|
||||
- make it look something like this https://getbootstrap.com/docs/4.0/examples/dashboard/
|
||||
|
||||
- easy clearing of shopping list
|
||||
|
||||
- figure out average usage of items over time and predict how much to buy in shopping list based on average expiry
|
||||
dates
|
||||
- figure out how long a certain item can last given current stock and average usage
|
||||
|
||||
- easily deploy somewhere (docker? on synology?)
|
||||
|
||||
## low
|
||||
- add recepies
|
||||
- shopping list created based on recepy ingredients
|
||||
- auto proposal of recepy based on next expiry dates
|
||||
- offer to buy things that have been on shopping list for a while online (in bulk/aggregated)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import PantryItem, PantryItemLine, Unit, Category, Location
|
||||
from .models import ShoppingListItem
|
||||
|
||||
|
||||
class PantryItemInLine(admin.TabularInline):
|
||||
|
@ -8,6 +9,12 @@ class PantryItemInLine(admin.TabularInline):
|
|||
extra = 1
|
||||
|
||||
|
||||
|
||||
class LocationInLine(admin.TabularInline):
|
||||
model = Location
|
||||
extra = 1
|
||||
|
||||
|
||||
def upper_case_name(obj):
|
||||
return obj.name.upper()
|
||||
upper_case_name.short_description = 'Name'
|
||||
|
@ -18,7 +25,7 @@ def capitalize_name(obj):
|
|||
upper_case_name.short_description = 'Name'
|
||||
|
||||
|
||||
class PantryItemInLineAdmin(admin.ModelAdmin):
|
||||
class PantryItemLineAdmin(admin.ModelAdmin):
|
||||
list_filter = ['expiry_date', 'pantry_item__unit', 'pantry_item', 'pantry_item__min_quantity']
|
||||
search_fields = ['info', 'pantry_item__name', 'pantry_item__info']
|
||||
autocomplete_fields = ['pantry_item']
|
||||
|
@ -47,6 +54,10 @@ class AutocompleteAdmin(admin.ModelAdmin):
|
|||
search_fields = ["name"]
|
||||
|
||||
|
||||
class LocationAdmin(AutocompleteAdmin):
|
||||
inlines = [LocationInLine]
|
||||
|
||||
|
||||
class PantryItemAdmin(admin.ModelAdmin):
|
||||
list_filter = ['category', 'unit', 'min_quantity', 'location']
|
||||
search_fields = ['info', 'name', 'category__name', 'unit__name']
|
||||
|
@ -72,8 +83,19 @@ class PantryItemAdmin(admin.ModelAdmin):
|
|||
'info',
|
||||
)
|
||||
|
||||
|
||||
class ShoppingListItemAdmin(PantryItemAdmin):
|
||||
|
||||
inlines = []
|
||||
fields = (
|
||||
'name',
|
||||
('min_quantity', 'unit'),
|
||||
'info',
|
||||
)
|
||||
|
||||
admin.site.register(PantryItem, PantryItemAdmin)
|
||||
admin.site.register(PantryItemLine, PantryItemInLineAdmin)
|
||||
admin.site.register(ShoppingListItem, ShoppingListItemAdmin)
|
||||
admin.site.register(PantryItemLine, PantryItemLineAdmin)
|
||||
admin.site.register(Unit, AutocompleteAdmin)
|
||||
admin.site.register(Category, AutocompleteAdmin)
|
||||
admin.site.register(Location, AutocompleteAdmin)
|
||||
admin.site.register(Location, LocationAdmin)
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 2.1.2 on 2018-10-07 11:08
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0016_auto_20180930_1409'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ShoppingListItem',
|
||||
fields=[
|
||||
('pantryitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='inventory.PantryItem')),
|
||||
],
|
||||
bases=('inventory.pantryitem',),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='pantryitem',
|
||||
name='location',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='inventory.Location'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 2.1.2 on 2018-10-09 10:32
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0017_auto_20181007_1108'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='pantryitem',
|
||||
name='category',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='inventory.Category'),
|
||||
),
|
||||
]
|
|
@ -1,4 +1,5 @@
|
|||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
|
@ -46,26 +47,32 @@ class Location(models.Model):
|
|||
return self.name + ' ' + str(self.in_location)
|
||||
return self.name
|
||||
|
||||
|
||||
class PantryItem(models.Model):
|
||||
"""A think you keep in your pantry """
|
||||
name = models.CharField(max_length=200)
|
||||
category = models.ForeignKey(Category, on_delete=models.PROTECT)
|
||||
min_quantity = models.IntegerField(default=1) #, decimal_places=3, max_digits=32)
|
||||
unit = models.ForeignKey(Unit, on_delete=models.PROTECT, null=True)
|
||||
info = models.CharField(max_length=200, null=True, blank=True)
|
||||
category = models.ForeignKey(Category, on_delete=models.PROTECT, null=True, blank=True)
|
||||
min_quantity = models.IntegerField(default=1)
|
||||
# some things don't have a fixed expiry date, like legumes or garlic, we can specify a default expiration for
|
||||
# this.
|
||||
# if expiry duration is set the expiration date for a pantryitemline will be set to now + duration on save
|
||||
# represents days
|
||||
expiry_duration = models.IntegerField(null=True, blank=True)
|
||||
# location is saved on a per pantryitem base, not pantryitemline
|
||||
name = models.CharField(max_length=200)
|
||||
unit = models.ForeignKey(Unit, on_delete=models.PROTECT, null=True)
|
||||
info = models.CharField(max_length=200, null=True, blank=True)
|
||||
# location is saved on a per item base, not itemline
|
||||
# you can have multiple pantries with subpantries
|
||||
# if a pantryitem moves to the kitchen or fridge it is no longer a pantry item
|
||||
location = models.ForeignKey(Location, on_delete=models.PROTECT, null=True)
|
||||
location = models.ForeignKey(Location, on_delete=models.PROTECT, null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ShoppingListItem(PantryItem):
|
||||
"""Items to buy now that aren't meant for the pantry but for imidiate usage"""
|
||||
pass
|
||||
|
||||
|
||||
class PantryItemLine(models.Model):
|
||||
# user?
|
||||
pantry_item = models.ForeignKey(PantryItem, on_delete=models.PROTECT)
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<a class="nav-link" href="{% url 'im:consume' %} ">Consume</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'admin:inventory_pantryitemline_add' %} ">Add</a>
|
||||
<a class="nav-link" href="{% url 'admin:inventory_pantryitem_changelist' %} ">Add</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
|
@ -49,8 +49,8 @@
|
|||
<a class="nav-link disabled" href="#">Disabled</a>
|
||||
</li!-->
|
||||
</ul>
|
||||
<form class="form-inline my-2 my-lg-0">
|
||||
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
|
||||
<form class="form-inline my-2 my-lg-0" action="{% url 'admin:inventory_pantryitem_changelist' %}" method="GET">
|
||||
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search" name="q">
|
||||
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
<li>
|
||||
{{ pi|title }} (We have
|
||||
{{ pi.total_quantity }} {{pi.unit}} but we want at least
|
||||
{{ pi.min_quantity }} {{pi.unit }})
|
||||
{{ pi.min_quantity }} {{pi.unit }} in {{ pi.location}} ({{pi.info}}))
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<a href="{% url 'admin:inventory_shoppinglistitem_add' %}">Add one off item to shoppinglist</a>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#requirements.txt
|
||||
Django>=2.1.2, < 2.2
|
||||
gunicorn
|
Loading…
Reference in New Issue