Compare commits

...

21 commits
tests ... main

Author SHA1 Message Date
Jens Timmerman af28efa2d4 added user system 2021-07-04 21:05:00 +02:00
jens 504ab9729d Update 'im/settings.py.prod' 2021-04-11 22:06:31 +00:00
jens 88d1ba7fd3 Update 'im/settings.py.prod' 2021-04-11 21:55:48 +00:00
jens 87880e6915 Update 'im/settings.py.prod' 2021-04-11 21:54:01 +00:00
jens f8a43996ad Merge pull request 'Update 'requirements.txt'' (#19) from jens-patch-2 into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #19
2021-02-19 10:55:02 +00:00
jens 0c9a04b56e Update 'requirements.txt'
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-19 10:40:13 +00:00
jens 38c6e8727c Update '.pylintrc'
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 10:22:47 +00:00
jens ebe68fc852 Merge pull request 'fix typos in readme' (#18) from Fre/im:typos into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #18
2021-02-10 10:19:17 +00:00
Fre Timmerman e74a331258 fix typos 2021-02-09 23:06:27 +01:00
jens 87b7551588 Merge pull request 'add default categories and units' (#17) from fixes into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #17
2020-11-08 17:02:33 +00:00
jens 2abe451b61 Merge branch 'main' into fixes
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-08 17:02:14 +00:00
Jens Timmerman 395c8f941e add default categories and units
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-08 18:00:53 +01:00
jens 921149d02a Merge pull request 'fixes' (#16) from fixes into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #16
2020-11-08 16:16:32 +00:00
Jens Timmerman d95b8059fd remove fixed todo's
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-08 17:11:40 +01:00
Jens Timmerman 9a48664042 fix flake8
Some checks failed
continuous-integration/drone/push Build is failing
2020-11-08 17:10:39 +01:00
Jens Timmerman f46626715e added consume view
Some checks failed
continuous-integration/drone/push Build is failing
2020-11-08 17:09:37 +01:00
Jens Timmerman bac1492b99 added intial migrations 2020-11-08 16:17:37 +01:00
jens 31662a5273 Merge pull request 'added tests' (#15) from tests into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #15
2020-11-08 14:48:47 +00:00
jens b997e970b0 Merge branch 'main' into tests
Some checks failed
continuous-integration/drone/push Build is failing
2020-11-08 14:47:52 +00:00
jens ab4f7cd15b Merge pull request 'Update 'README.md'' (#13) from jens-patch-1 into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #13
2020-11-08 14:41:02 +00:00
jens f6ebe47eca Update 'README.md'
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-25 19:38:01 +00:00
37 changed files with 549 additions and 31 deletions

View file

@ -1,5 +1,6 @@
[MASTER]
load-plugins=pylint_django
django-settings-module=im.settings
[FORMAT]
max-line-length=120

View file

@ -12,7 +12,7 @@ The object of this django app is to keep track of which goods you own, when they
- 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 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
@ -24,7 +24,7 @@ The object of this django app is to keep track of which goods you own, when they
```
dnf install -y python3-pip git
pip3 install django
git clone git@github.com:JensTimmerman/im.git
git clone https://gitea.caret.be/jens/im.git
cd im
python3 manage.py migrate
@ -44,6 +44,7 @@ Now you are running the development server, Visit http://127.0.0.1:8000/admin/
# installation
If you use ansible: see https://gitea.caret.be/jens/ansible-role-im
```
dnf install python3-pip git
pip3 install django psycopg2 gunicorn
@ -156,7 +157,7 @@ browse to https://im.yourdomain
# feature requests
## High
- Add itmes per location, click a location and then start adding items with the location list prepopulated
- Add items per location, click a location and then start adding items with the location list prepopulated
- auto refresh dropdown boxes after items have been added in admin view, so page reload is not needed
- auto parse dates in different date formats when given in a datefield. e.g. 20 12 2020 or 2020 12 20 to 2020-12-20
- also make ipad show the numbered keyboard here
@ -198,9 +199,9 @@ browse to https://im.yourdomain
- easily deploy somewhere
## low
- add recepies
- shopping list created based on recepy ingredients
- auto proposal of recepy based on next expiry dates
- add recipes
- shopping list created based on recipe ingredients
- auto proposal of recipe based on next expiry dates
- offer to buy things that have been on shopping list for a while online (in bulk/aggregated)
- 3d view of where things are in space
- vr to help locate things

View file

@ -25,14 +25,19 @@ SECRET_KEY = '-h%7n38#ij*7$pzkv=8-+9axa6o6fk9e4z3x676774f&06-di9'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
ALLOWED_HOSTS = [
'0.0.0.0',
]
LOGIN_REDIRECT_URL = "im:index"
LOGOUT_REDIRECT_URL = "im:index"
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'users.apps.UsersConfig',
'django.contrib.auth',
'django.contrib.admin',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
@ -124,3 +129,7 @@ USE_TZ = True
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'
EMAIL_HOST = 'mail.caret.be'
EMAIL_PORT = '25'

View file

@ -30,6 +30,10 @@ ALLOWED_HOSTS = [
]
ADMINS = [('admin', '{{im_admin_email}}')]
EMAIL_HOST = '{{im_mail_server}}'
# Application definition
INSTALLED_APPS = [

View file

@ -16,7 +16,11 @@ Including another URLconf
from django.contrib import admin
from django.urls import include, path
from users.views import register
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('django.contrib.auth.urls')),
path("register/", register, name="register"),
path('', include('inventory.urls')),
]

1
initialdata.json Normal file
View file

@ -0,0 +1 @@
[{"model": "inventory.category", "pk": 1, "fields": {"name": "UNCATEGORIZED"}}, {"model": "inventory.category", "pk": 2, "fields": {"name": "GENRAL_STOCK"}}, {"model": "inventory.category", "pk": 3, "fields": {"name": "CANS"}}, {"model": "inventory.category", "pk": 4, "fields": {"name": "BREAKFAST"}}, {"model": "inventory.category", "pk": 5, "fields": {"name": "SAVORIES"}}, {"model": "inventory.category", "pk": 6, "fields": {"name": "SWEETS"}}, {"model": "inventory.category", "pk": 7, "fields": {"name": "SNACKS"}}, {"model": "inventory.category", "pk": 8, "fields": {"name": "SPICES"}}, {"model": "inventory.category", "pk": 9, "fields": {"name": "DRINKS"}}, {"model": "inventory.unit", "pk": 1, "fields": {"name": "Mililiter"}}, {"model": "inventory.unit", "pk": 2, "fields": {"name": "Grams"}}, {"model": "inventory.unit", "pk": 3, "fields": {"name": "Roll"}}, {"model": "inventory.unit", "pk": 4, "fields": {"name": "Tube"}}, {"model": "inventory.unit", "pk": 5, "fields": {"name": "Bag"}}, {"model": "inventory.unit", "pk": 6, "fields": {"name": "Bar"}}, {"model": "inventory.unit", "pk": 7, "fields": {"name": "Pack"}}, {"model": "inventory.location", "pk": 1, "fields": {"name": "Caret", "in_location": null}}]

View file

@ -29,7 +29,7 @@ upper_case_name.short_description = 'Name'
class PantryItemLineAdmin(admin.ModelAdmin):
list_filter = ['expiry_date', 'pantry_item__unit', 'pantry_item', 'pantry_item__min_quantity']
list_filter = ['expiry_date', 'pantry_item__unit', 'pantry_item', 'pantry_item__min_quantity', 'location']
search_fields = ['info', 'pantry_item__name', 'pantry_item__info']
autocomplete_fields = ['pantry_item']
@ -62,13 +62,12 @@ class LocationAdmin(AutocompleteAdmin):
class PantryItemAdmin(admin.ModelAdmin):
list_filter = ['category', 'unit', 'min_quantity', 'location']
list_filter = ['category', 'unit', 'min_quantity']
search_fields = ['info', 'name', 'category__name', 'unit__name']
autocomplete_fields = ['category', 'unit']
inlines = [PantryItemInLine]
# TODO: make category a model
# autocomplete_fields = ['category',]
autocomplete_fields = ['category', ]
fields = (
'name',
'category',

View file

@ -0,0 +1,72 @@
# Generated by Django 3.1.3 on 2020-11-08 15:14
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name='Location',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=200, null=True)),
('in_location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='inventory.location')),
],
),
migrations.CreateModel(
name='PantryItem',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('min_quantity', models.IntegerField(default=1)),
('expiry_duration', models.IntegerField(blank=True, null=True)),
('name', models.CharField(max_length=200)),
('info', models.CharField(blank=True, max_length=200, null=True)),
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='inventory.category')),
('location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='inventory.location')),
],
),
migrations.CreateModel(
name='Unit',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
],
),
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.CreateModel(
name='PantryItemLine',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField(default=1)),
('expiry_date', models.DateField(blank=True, null=True)),
('size', models.IntegerField(default=1)),
('info', models.CharField(blank=True, max_length=200, null=True)),
('pantry_item', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='inventory.pantryitem')),
],
),
migrations.AddField(
model_name='pantryitem',
name='unit',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='inventory.unit'),
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 3.2.4 on 2021-07-04 18:10
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='pantryitem',
name='location',
),
migrations.AddField(
model_name='pantryitemline',
name='location',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='inventory.location'),
),
]

View file

View file

@ -39,7 +39,7 @@ class Unit(models.Model):
class Location(models.Model):
"""Location for a pantry item line"""
name = models.CharField(max_length=200, null=True, blank=True)
name = models.CharField(max_length=200)
in_location = models.ForeignKey("self", on_delete=models.PROTECT, null=True, blank=True)
def __str__(self):
@ -47,6 +47,9 @@ class Location(models.Model):
return self.name + ' ' + str(self.in_location)
return self.name
class Meta:
unique_together = ('name', 'in_location')
class PantryItem(models.Model):
"""A think you keep in your pantry """
@ -60,9 +63,6 @@ class PantryItem(models.Model):
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
location = models.ForeignKey(Location, on_delete=models.PROTECT, null=True, blank=True)
def __str__(self):
return self.name
@ -81,11 +81,13 @@ class PantryItemLine(models.Model):
size = models.IntegerField(default=1)
info = models.CharField(max_length=200, null=True, blank=True)
location = models.ForeignKey(Location, on_delete=models.PROTECT, null=True, blank=True)
def unit(self):
return self.pantry_item.unit
def get_absolute_url(self):
return reverse('pantryitemlinedetail', kwargs={'pk': self.pk})
return reverse('im:pantryitemlinedetail', kwargs={'pk': self.pk})
def __str__(self):
return ' '.join([str(x) for x in [self.pantry_item.name, self.quantity, 'X',

View file

@ -0,0 +1,89 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>Inventory Management</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/">Inventory Management</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'im:consumelist' %} ">Consume</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'im:additemline' %} ">Add</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'im:shoppinglist' %} ">Shopping List</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'im:expirations' %} ">Expirations</a>
</li>
<li class="nav-item">
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'logout' %}">Logout {{ user.username }}</a>
<li class="nav-item">
<a class="nav-link" href="{% url 'password_change' %} ">Change Password</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'admin:index' %} ">Admin</a>
</li>
{% else %}
<a class="nav-link" href="{% url 'login' %}">Login</a>
{% endif %}
</li>
<!--li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Dropdown
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#">Disabled</a>
</li!-->
</ul>
<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>
</nav>
<div class="container">
<div class="row justify-content-center">
<div class="col-8">
{% block content %}
BASE TEMPLATE
{% endblock %}
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossorigin="anonymous"></script>
</body>
</html>

View file

@ -18,10 +18,10 @@
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'im:consume' %} ">Consume</a>
<a class="nav-link" href="{% url 'im:consumelist' %} ">Consume</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'admin:inventory_pantryitem_changelist' %} ">Add</a>
<a class="nav-link" href="{% url 'im:additemline' %} ">Add</a>
</li>
<li class="nav-item">
@ -31,8 +31,22 @@
<a class="nav-link" href="{% url 'im:expirations' %} ">Expirations</a>
</li>
<li class="nav-item">
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'logout' %}">Logout {{ user.username }}</a>
<li class="nav-item">
<a class="nav-link" href="{% url 'password_change' %} ">Change Password</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'admin:index' %} ">Admin</a>
</li>
</li>
{% else %}
<a class="nav-link" href="{% url 'login' %}">Login</a>
{% endif %}
</li>
<!--li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">

View file

@ -0,0 +1,12 @@
{% extends 'inventory/base.html' %}
{% block content %}
<h2>Add Category</h2>
<form action="." method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
{% endblock %}

View file

@ -1,5 +1,10 @@
{% extends 'inventory/base.html' %}
{% block content %}
TODO: add form to mark items as consumed
<h1>Consume</h1>
Change quantity for "{{ object }}"
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Update">
</form>
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends 'inventory/base.html' %}
{% block content %}
<h1>Consume</h1>
{% if pis %}
{% regroup pis by expiry_date as pis_by_date %}
{% for date in pis_by_date %}
<div class="content">
<h2>{{ date.grouper|date:"d F Y" }}</h2>
<ul>
{% for pi in date.list %}
<li><a href="{% url 'im:consume' pi.id %}">{{ pi|title}}</a></li>
{% endfor %}
</ul>
</div>
{% endfor %}
{% else %}
<p>No Pantry Items are available.</p>
{% endif %}
{% endblock %}

View file

@ -17,5 +17,16 @@ Expirations: See items that will expire soon
</p>
<p>
TODO: add items to your shopping list below
</p>
<p>
TODO: see expirted items below?
</p>
<p>
TODO: when no categories are found, show getting started, otherwise nit.
You will probably want to add some default Categories and Units
run TODO: https://pypi.org/project/django-smuggler/
</p>
{% endblock %}

View file

@ -0,0 +1,12 @@
{% extends 'inventory/base.html' %}
{% block content %}
<h2>Add Location</h2>
<form action="." method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
{% endblock %}

View file

@ -0,0 +1,13 @@
{% extends 'inventory/base.html' %}
{% block content %}
If you can not find the Category you want to add a new Line item from, add a new one <a href="../addcategory/">here</a>
<h2>Add Item Line</h2>
<form action="/additemline/" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
{% endblock %}

View file

@ -0,0 +1,6 @@
{% extends 'inventory/base.html' %}
{% block content %}
<h1>Item Details</h1>
{{ object }}
{% endblock %}

View file

@ -0,0 +1,17 @@
{% extends 'inventory/base.html' %}
{% block content %}
<p>
If you can not find the Pantry Item you want to add a new Line item from, add a new one <a href="../additem/">here</a>
</p>
<p>
If you can not find the Location you want to add a new Line item from, add a new one <a href="../addlocation/">here</a>
</p>
<h2>Add Item Line</h2>
<form action="/additemline/" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
{% endblock %}

View file

@ -0,0 +1,10 @@
{% extends 'inventory/base.html' %}
{% block content %}
<h2>Units</h2>
<ul>
{% for unit in object_list %}
<li>{{ unit.name }}</li>
{% endfor %}
</ul>
{% endblock %}

View file

@ -4,10 +4,14 @@ from . import views
app_name = 'im'
urlpatterns = [
path('', views.index, name='index'),
path('consume/', views.consume, name='consume'),
path('items/<pk>', views.PantryItemLineView.as_view(), name='pantryitemlinedetail'),
path('consume/', views.ConsumeList.as_view(), name='consumelist'),
path('consume/<pk>/', views.Consume.as_view(), name='consume'),
path('shoppinglist/', views.Shoppinglist.as_view(), name='shoppinglist'),
# TODO: add exiperes before X date?
# TODO: add categories
# TODO: add pantry item selection
path('expirations/', views.Expirations.as_view(), name='expirations'),
path('units/', views.UnitView.as_view(), name='units'),
path('additemline/', views.PantryItemLineCreateView.as_view(), name='additemline'),
path('additem/', views.PantryItemCreateView.as_view(), name='additem'),
path('addcategory/', views.CategoryCreateView.as_view(), name='addcategory'),
path('addlocation/', views.LocationCreateView.as_view(), name='addlocation'),
]

View file

@ -3,7 +3,43 @@ from django.views import generic
from django.db.models import F, Sum, Q
from .models import PantryItem, PantryItemLine
from django.views.generic import ListView
from inventory.models import Unit, Location
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from .models import PantryItem, PantryItemLine, Category
class PantryItemLineCreateView(LoginRequiredMixin, CreateView):
model = PantryItemLine
fields = '__all__'
success_url = '.'
class PantryItemCreateView(LoginRequiredMixin, CreateView):
model = PantryItem
fields = '__all__'
success_url = '.'
class CategoryCreateView(LoginRequiredMixin, CreateView):
model = Category
fields = '__all__'
success_url = '.'
class LocationCreateView(LoginRequiredMixin, CreateView):
model = Location
fields = '__all__'
success_url = '.'
class UnitView(ListView):
model = Unit
# Create your views here.
@ -12,10 +48,6 @@ def index(request):
return render(request, "inventory/index.html")
def consume(request):
return render(request, "inventory/consume.html")
class Shoppinglist(generic.ListView):
template_name = "inventory/shoppinglist.html"
context_object_name = 'pis'
@ -36,3 +68,25 @@ class Expirations(generic.ListView):
def get_queryset(self):
return PantryItemLine.objects.exclude(expiry_date__isnull=True).exclude(quantity=0).order_by('expiry_date')
class ConsumeList(generic.ListView):
template_name = "inventory/consumelist.html"
context_object_name = 'pis'
def get_queryset(self):
return PantryItemLine.objects.exclude(quantity=0).order_by('expiry_date')
class Consume(generic.UpdateView):
template_name = "inventory/consume.html"
fields = ['quantity']
context_object_name = 'pis'
def get_queryset(self):
return PantryItemLine.objects.exclude(quantity=0).order_by('expiry_date')
class PantryItemLineView(generic.DetailView):
model = PantryItemLine

View file

@ -1,3 +1,3 @@
#requirements.txt
django>=2.2.8
django>=2.2.19
gunicorn

0
users/__init__.py Normal file
View file

5
users/apps.py Normal file
View file

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

5
users/forms.py Normal file
View file

@ -0,0 +1,5 @@
from django.contrib.auth.forms import UserCreationForm
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
fields = UserCreationForm.Meta.fields + ("email",)

View file

@ -0,0 +1,15 @@
{% extends 'inventory/base.html' %}
{% block content %}
<h2>Login</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Login">
</form>
<a href="{% url 'im:index' %}">Back to index</a>
<a href="{% url 'register' %}">Register</a>
<a href="{% url 'password_reset' %}">Reset Password</a>
{% endblock %}

View file

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<h2>Password changed</h2>
<a href="{% url 'im:index' %}">Back to index</a>
{% endblock %}

View file

@ -0,0 +1,13 @@
{% extends 'inventory/base.html' %}
{% block content %}
<h2>Change Password</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Change">
</form>
<a href="{% url 'im:index' %}">Back to index</a>
{% endblock %}

View file

@ -0,0 +1,8 @@
{% extends 'base.html' %}
{% block content %}
<h2>Password reset complete</h2>
<a href="{% url 'login' %}">Back to login</a>
{% endblock %}

View file

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block content %}
<h2>Confirm password reset</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Confirm">
</form>
{% endblock %}

View file

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<h2>Password reset done</h2>
<a href="{% url 'login' %}">Back to login</a>
{% endblock %}

View file

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block content %}
<h2>Send password reset link</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Reset">
</form>
<a href="{% url 'im:index' %}">Back to index</a>
{% endblock %}

View file

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block content %}
<h2>Register</h2>
<form method="post">
{% csrf_token %}
{{form}}
<input type="submit" value="Register">
</form>
<a href="{% url 'login' %}">Back to login</a>
{% endblock %}

37
users/views.py Normal file
View file

@ -0,0 +1,37 @@
from django.contrib.auth import login
from django.shortcuts import redirect, render
from django.urls import reverse
from users.forms import CustomUserCreationForm
def dashboard(request):
return render(request, "users/dashboard.html")
def register(request):
if request.method == "GET":
return render(
request, "users/register.html",
{"form": CustomUserCreationForm}
)
elif request.method == "POST":
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect(reverse("index"))