Skip to content

Commit

Permalink
FIXUP
Browse files Browse the repository at this point in the history
  • Loading branch information
Knalltuete5000 committed Aug 21, 2023
1 parent c7a330e commit 53ece8b
Show file tree
Hide file tree
Showing 44 changed files with 4,270 additions and 4,439 deletions.
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,15 @@ mediafiles/

# Deployment

bin/
share/
lib64/
lib64
\.env
staticfiles/
postgresql/

pyvenv.cfg
docker-compose.yml

/docker-compose.override.yml
vue/node_modules
Expand Down
28 changes: 25 additions & 3 deletions cookbook/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
RecipeBookEntry, RecipeImport, SearchPreference, ShareLink, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, Supermarket,
SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog, TelegramBot,
Unit, UnitConversion, UserFile, UserPreference, UserSpace, ViewLog)
Unit, UnitConversion, UserFile, UserPreference, UserSpace, ViewLog,
Equipment, EquipmentInheritField, EquipmentSet,)


class CustomUserAdmin(UserAdmin):
Expand Down Expand Up @@ -122,19 +123,22 @@ class SyncLogAdmin(admin.ModelAdmin):
admin.site.register(SyncLog, SyncLogAdmin)


@admin.action(description='Temporarily ENABLE sorting on Foods and Keywords.')
@admin.action(description='Temporarily ENABLE sorting on Equipments, Foods and Keywords.')
def enable_tree_sorting(modeladmin, request, queryset):
Food.node_order_by = ['name']
Keyword.node_order_by = ['name']
Equipment.node_order_by = ['name']
with scopes_disabled():
Food.fix_tree(fix_paths=True)
Keyword.fix_tree(fix_paths=True)
Equipment.fix_tree(fix_paths=True)


@admin.action(description='Temporarily DISABLE sorting on Foods and Keywords.')
@admin.action(description='Temporarily DISABLE sorting on Equipments, Foods and Keywords.')
def disable_tree_sorting(modeladmin, request, queryset):
Food.node_order_by = []
Keyword.node_order_by = []
Equipment.node_order_by = []


@admin.action(description='Fix problems and sort tree by name')
Expand Down Expand Up @@ -228,6 +232,16 @@ def delete_unattached_ingredients(modeladmin, request, queryset):
Ingredient.objects.filter(step__recipe=None).delete()


class EquipmentAdmin(admin.ModelAdmin):
form = movenodeform_factory(Keyword)
ordering = ('space', 'path',)
search_fields = ('name',)
actions = [sort_tree, enable_tree_sorting, disable_tree_sorting]


admin.site.register(Equipment, EquipmentAdmin)


class IngredientAdmin(admin.ModelAdmin):
list_display = ('food', 'amount', 'unit')
search_fields = ('food__name', 'unit__name')
Expand All @@ -237,6 +251,14 @@ class IngredientAdmin(admin.ModelAdmin):
admin.site.register(Ingredient, IngredientAdmin)


class EquipmentSetAdmin(admin.ModelAdmin):
list_display = ('equipment', 'amount')
search_fields = ('equipment__name',)


admin.site.register(EquipmentSet, EquipmentSetAdmin)


class CommentAdmin(admin.ModelAdmin):
list_display = ('recipe', 'name', 'created_at')
search_fields = ('text', 'created_by__username')
Expand Down
13 changes: 9 additions & 4 deletions cookbook/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
from hcaptcha.fields import hCaptchaField

from .models import (Comment, Food, InviteLink, Keyword, MealPlan, MealType, Recipe, RecipeBook,
from .models import (Comment, Food, Equipment, InviteLink, Keyword, MealPlan, MealType, Recipe, RecipeBook,
RecipeBookEntry, SearchPreference, Space, Storage, Sync, User, UserPreference)


Expand Down Expand Up @@ -543,22 +543,27 @@ class SpacePreferenceForm(forms.ModelForm):
prefix = 'space'
reset_food_inherit = forms.BooleanField(label=_("Reset Food Inheritance"), initial=False, required=False,
help_text=_("Reset all food to inherit the fields configured."))
reset_equipment_inherit = forms.BooleanField(label=_("Reset Equipment Inheritance"), initial=False, required=False,
help_text=_("Reset all equipment to inherit the fields configured."))

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # populates the post
self.fields['food_inherit'].queryset = Food.inheritable_fields
self.fields['equipment_inherit'].queryset = Equipment.inheritable_fields

class Meta:
model = Space

fields = ('food_inherit', 'reset_food_inherit', 'show_facet_count', 'use_plural')
fields = ('food_inherit', 'reset_food_inherit', 'equipment_inherit', 'reset_equipment_inherit', 'show_facet_count', 'use_plural')

help_texts = {
'food_inherit': _('Fields on food that should be inherited by default.'),
'equipment_inherit': _('Fields on equipment that should be inherited by default.'),
'show_facet_count': _('Show recipe counts on search filters'),
'use_plural': _('Use the plural form for units and food inside this space.'),
'use_plural': _('Use the plural form for units, food and equipment inside this space.'),
}

widgets = {
'food_inherit': MultiSelectWidget
'food_inherit': MultiSelectWidget,
'equipment_inherit': MultiSelectWidget
}
5 changes: 4 additions & 1 deletion cookbook/helper/dal.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from cookbook.models import Food, Keyword, Recipe, Unit
from cookbook.models import Equipment, Food, Keyword, Recipe, Unit

from dal import autocomplete

Expand All @@ -17,6 +17,9 @@ def get_queryset(self):

return qs

class EquipmentSetAutocomplete(BaseAutocomplete):
model = Equipment


class KeywordAutocomplete(BaseAutocomplete):
model = Keyword
Expand Down
205 changes: 205 additions & 0 deletions cookbook/helper/equipmentset_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import re
import string
import unicodedata

from django.core.cache import caches

from cookbook.models import Equipment, EquipmentSet, Automation

class EquipmentSetParser:
request = None
ignore_rules = False,
equipment_aliases = {}

def __init__(self, request, cache_mode, ignore_automations=False):
self.request = request
self.ignore_rules = ignore_automations

if cache_mode:
EQUIPMENT_CACHE_KEY = f'automation_equipment_alias_{self.request.space.pk}'
if c:= caches['default'].get(EQUIPMENT_CACHE_KEY, None):
self.equipment_aliases = c
caches['default'].touch(EQUIPMENT_CACHE_KEY, 30)
else:
for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.EQUIPMENT_ALIAS).only('param_1', 'param_2').order_by('order').all():
self.equipment_aliases[a.param_1] = a.param_2
caches['default'].set(EQUIPMENT_CACHE_KEY, self.equipment_aliases, 30)
else:
self.equipment_aliases = {}

def apply_equipment_automation(self, equipment):
if self.ignore_rules:
return equipment
else:
if self.equipment_aliases:
try:
return self.equipment_aliases[equipment]
except KeyError:
return equipment
else:
if automation := Automation.objects.filter(space=self.request.space, type=Automation.EQUIPMENT_ALIAS, param_1=equipment, disabled=False).order_by('order').first():
return automation.param_2
return equipment

def get_equipment(self, equipment):
if not equipment:
return None
if len(equipment) > 0:
f, created = Equipment.objects.get_or_create(name=self.apply_equipment_automation(equipment), space=self.request.space)
return f
return None

def parse_equipment_with_comma(self, tokens):
equipment = ''
note = ''
start = 0
# search for first occurrence of an argument ending in a comma
while start < len(tokens) and not tokens[start].endswith(','):
start += 1
if start == len(tokens):
# no token ending in a comma found -> use everything as equipment
equipment = ' '.join(tokens)
else:
equipment = ' '.join(tokens[:start + 1])[:-1]
note = ' '.join(tokens[start + 1:])
return equipment, note

def parse_equipment(self, tokens):
equipment = ''
note = ''
if tokens[-1].endswith(')'):
# Check if the matching opening bracket is in the same token
if (not tokens[-1].startswith('(')) and ('(' in tokens[-1]):
return self.parse_equipment_with_comma(tokens)
# last argument ends with closing bracket -> look for opening bracket
start = len(tokens) - 1
while not tokens[start].startswith('(') and not start == 0:
start -= 1
if start == 0:
# the whole list is wrapped in brackets -> assume it is an error (e.g. assumed first argument was the unit) # noqa: E501
raise ValueError
elif start < 0:
# no opening bracket anywhere -> just ignore the last bracket
equipment, note = self.parse_equipment_with_comma(tokens)
else:
# opening bracket found -> split in equipment and note, remove brackets from note # noqa: E501
note = ' '.join(tokens[start:])[1:-1]
equipment = ' '.join(tokens[:start])
else:
equipment, note = self.parse_equipment_with_comma(tokens)
return equipment, note

def parse_fraction(self, x):
if len(x) == 1 and 'fraction' in unicodedata.decomposition(x):
frac_split = unicodedata.decomposition(x[-1:]).split()
return (float((frac_split[1]).replace('003', ''))
/ float((frac_split[3]).replace('003', '')))
else:
frac_split = x.split('/')
if not len(frac_split) == 2:
raise ValueError
try:
return int(frac_split[0]) / int(frac_split[1])
except ZeroDivisionError:
raise ValueError

def parse_amount(self, x):
amount = 0
if x.strip() == '':
return amount

end = 0
while (end < len(x) and (x[end] in string.digits
or (
(x[end] == '.' or x[end] == ',' or x[end] == '/')
and end + 1 < len(x)
and x[end + 1] in string.digits
))):
end += 1
if end > 0:
if "/" in x[:end]:
amount = self.parse_fraction(x[:end])
else:
amount = float(x[:end].replace(',', '.'))
else:
amount = self.parse_fraction(x[0])
end += 1
if end < len(x):
amount += self.parse_fraction(x[end])
return amount

def parse(self, equipmentset):
# initialize default values
amount = 0
equipment = ''
note = ''

if len(equipmentset) == 0:
raise ValueError('string to parse cannot be empty')

if len(equipmentset) < 1000 and re.search(r'^([^\W\d_])+(.)*[1-9](\d)*\s*([^\W\d_])+', equipmentset):
match = re.search(r'[1-9](\d)*\s*([^\W\d_])+', equipmentset)
print(f'reording from {equipmentset} to {equipmentset[match.start():match.end()] + " " + equipmentset.replace(equipmentset[match.start():match.end()], "")}')
equipmentset = equipmentset[match.start():match.end()] + ' ' + equipmentset.replace(equipmentset[match.start():match.end()], '')

# if the string contains parenthesis early on remove it and place it at the end
# because its likely some kind of note
if re.match('(.){1,6}\s\((.[^\(\)])+\)\s', equipmentset):
match = re.search('\((.[^\(])+\)', equipmentset)
equipmentset = equipmentset[:match.start()] + equipmentset[match.end():] + ' ' + equipmentset[match.start():match.end()]

# leading spaces before commas result in extra tokens, clean them out
equipmentset = equipmentset.replace(' ,', ',')

# handle "(from) - (to)" amounts by using the minimum amount and adding the range to the description
# "10.5 - 200 g XYZ" => "100 g XYZ (10.5 - 200)"
equipmentset = re.sub("^(\d+|\d+[\\.,]\d+) - (\d+|\d+[\\.,]\d+) (.*)", "\\1 \\3 (\\1 - \\2)", equipmentset)

# if amount and unit are connected add space in between
if re.match('([0-9])+([A-z])+\s', equipmentset):
equipmentset = re.sub(r'(?<=([a-z])|\d)(?=(?(1)\d|[a-z]))', ' ', equipmentset)

tokens = equipmentset.split() # split at each space into tokens
if len(tokens) == 1:
# there only is one argument, that must be the equipment
equipment = tokens[0]
else:
try:
# try to parse first argument as amount
amount = self.parse_amount(tokens[0])
# only try to parse second argument as amount if there are at least
# three arguments if it already has a unit there can't be
# a fraction for the amount
if len(tokens) > 2:
try:
# try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½'
amount += self.parse_fraction(tokens[1])
equipment, note = self.parse_equipment(tokens[2:])
except ValueError:
# assume that units can't end with a comma
if not tokens[1].endswith(','):
equipment, note = self.parse_equipment(tokens[1:])
else:
equipment, note = self.parse_equipment(tokens[1:])
else:
equipment = tokens[1]
except ValueError:
try:
equipment, note = self.parse_equipment(tokens)
except ValueError:
equipment = ' '.join(tokens[1:])

equipment = self.apply_equipment_automation(equipment.strip())
if len(equipment) > Equipment._meta.get_field('name').max_length: # test if equipment name is to long
# try splitting it at a space and taking only the first arg
if len(equipment.split()) > 1 and len(equipment.split()[0]) < Equipment._meta.get_field('name').max_length:
note = ' '.join(equipment.split()[1:]) + ' ' + note
equipment = equipment.split()[0]
else:
note = equipment + ' ' + note
equipment = equipment[:Equipment._meta.get_field('name').max_length]

if len(equipment.strip()) == 0:
raise ValueError(f'Error parsing string {equipmentset}, equipment cannot be empty')

return amount, equipment, note[:EquipmentSet._meta.get_field('note').max_length].strip()
5 changes: 5 additions & 0 deletions cookbook/helper/open_data_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ def import_supermarket(self):

return supermarkets

def import_equipment(self):
datatype = 'equipment'
for k in list(self.data[datatype].keys()):
pass

def import_food(self):
identifier_list = []
datatype = 'food'
Expand Down
Loading

0 comments on commit 53ece8b

Please sign in to comment.