Compare commits

...

9 Commits

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
continuous-integration/drone/push Build is passing Details
Reviewed-on: #19
2021-02-19 10:55:02 +00:00
jens 0c9a04b56e Update 'requirements.txt'
continuous-integration/drone/push Build is passing Details
2021-02-19 10:40:13 +00:00
jens 38c6e8727c Update '.pylintrc'
continuous-integration/drone/push Build is passing Details
2021-02-10 10:22:47 +00:00
jens ebe68fc852 Merge pull request 'fix typos in readme' (#18) from Fre/im:typos into main
continuous-integration/drone/push Build is failing Details
Reviewed-on: #18
2021-02-10 10:19:17 +00:00
Fre Timmerman e74a331258 fix typos 2021-02-09 23:06:27 +01:00
31 changed files with 414 additions and 18 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
@ -157,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
@ -199,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')),
]

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,7 +62,7 @@ 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]

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

@ -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,6 +81,8 @@ 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

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

@ -21,7 +21,7 @@
<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

@ -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,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

@ -9,4 +9,9 @@ urlpatterns = [
path('consume/<pk>/', views.Consume.as_view(), name='consume'),
path('shoppinglist/', views.Shoppinglist.as_view(), name='shoppinglist'),
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.

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"))