Project files

This commit is contained in:
2023-11-09 18:47:11 +01:00
parent 695abe054b
commit c415135aae
8554 changed files with 858111 additions and 0 deletions

View File

View File

@@ -0,0 +1,24 @@
from django.contrib import admin
from .models import *
from treebeard.admin import TreeAdmin
from treebeard.forms import movenodeform_factory
# Register your models here.
class MyAdmin(TreeAdmin):
form = movenodeform_factory(Category)
admin.site.register(Purchase)
admin.site.register(ReceipeImage)
admin.site.register(PurchaseArticle)
admin.site.register(Market)
admin.site.register(Article)
admin.site.register(ArticleMaps)
admin.site.register(Ingredients)
admin.site.register(IngredientsArticle)
admin.site.register(Allergenes)
admin.site.register(Labels)
admin.site.register(Brand)
admin.site.register(NutritionalValues)
admin.site.register(Packaging)
admin.site.register(PackagingArticle)
admin.site.register(Category, MyAdmin)

View File

@@ -0,0 +1,5 @@
from django.contrib import admin
from .models import Purchase
# Register your models here.
admin.site.register(Purchase)

View File

@@ -0,0 +1,23 @@
import os
import requests
#url = 'http://192.168.178.85:8000/api/postImage/'
#url = 'http://127.0.0.1:8000/api/postImage/'
path_img = '/home/elena/Documents/Projects/ReceipeScanner/Server/testbild.jpg'
'''
with open(path_img, 'rb') as img:
name_img = os.path.basename(path_img)
files= {'uploadedFile': (name_img,img,'multipart/form-data',{'Expires': '0'}) }
with requests.Session() as s:
r = s.post(url,files=files)
print(r.status_code)
'''
#url = 'http://192.168.178.85:8000/api/scanner/receipeComplete/'
#url = 'http://192.168.178.85:8000/api/scanner/processAgain/'
url = 'http://127.0.0.1:8000/api/scanner/processAgain/'
with requests.Session() as s:
r = s.post(url,data={'filename':'receipe-upload-_oqp9us3.jpg'})
print(r.status_code)

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ReceipeConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'receipe'

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun May 1 13:27:28 2022
@author: elena
"""
#Not in use atm
class Market():
def __init__(self):
self.id = -1
self.name = ''
self.street = ''
self.street_number = 0
self.zip = 0
self.city = ''
self.phone = ''
class noDBArticle():
def __init__(self):
self.id = -1
self.articleId = -1
self.name = ''
self.quantity = 0
self.price = 0.0
self.nameString = ''
self.nameBBox = None
self.priceString = ''
self.priceBBox = None
def __hash__(self):
return hash((self.id, self.name))
def __eq__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
return self.id == other.id and self.name == other.name

View File

@@ -0,0 +1,432 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 30 13:13:23 2022
@author: elena
"""
import os
import uuid
import hashlib
import datetime
from filelock import FileLock
from django.conf import settings
#from django.db.models import Q
from django.db import transaction
from django.utils import timezone
from .models import ReceipeImage, Article, Purchase, PurchaseArticle,ArticleMaps
from .loggers import LoggingMixin
from .parser import ReceipeParser
from .image_processing import crop_binarize, crop_binarize_scanner
from .file_handling import create_source_path_directory
MESSAGE_RECEIPE_ALREADY_EXISTS = "receipe_already_exists"
MESSAGE_FILE_NOT_FOUND = "file_not_found"
MESSAGE_PRE_CONSUME_SCRIPT_NOT_FOUND = "pre_consume_script_not_found"
MESSAGE_PRE_CONSUME_SCRIPT_ERROR = "pre_consume_script_error"
MESSAGE_POST_CONSUME_SCRIPT_NOT_FOUND = "post_consume_script_not_found"
MESSAGE_POST_CONSUME_SCRIPT_ERROR = "post_consume_script_error"
MESSAGE_NEW_FILE = "new_file"
MESSAGE_UNSUPPORTED_TYPE = "unsupported_type"
MESSAGE_PARSING_RECEIPE = "parsing_receipe"
MESSAGE_GENERATING_THUMBNAIL = "generating_thumbnail"
MESSAGE_PARSE_DATE = "parse_date"
MESSAGE_SAVE_RECEIPE = "save_receipe"
MESSAGE_FINISHED = "finished"
class ConsumerError(Exception):
pass
class Consumer(LoggingMixin):
logging_name = "receipeServer.consumer"
def _send_progress(self, current_progress, max_progress, status,
message=None, document_id=None):
payload = {
'filename': os.path.basename(self.filename) if self.filename else None, # NOQA: E501
'task_id': self.task_id,
'current_progress': current_progress,
'max_progress': max_progress,
'status': status,
'message': message,
'document_id': document_id
}
#async_to_sync(self.channel_layer.group_send)("status_updates",
# {'type': 'status_update',
# 'data': payload})
def __init__(self):
super().__init__()
self.path = None
self.filename = None
self.task_id = None
#self.channel_layer = get_channel_layer()
def _fail(self, message, log_message=None, exc_info=None):
self._send_progress(100, 100, 'FAILED', message)
self.log("error", log_message or message, exc_info=exc_info)
raise ConsumerError(f"{self.filename}: {log_message or message}")
def pre_check_file_exists(self):
if not os.path.isfile(self.path):
self._fail(
MESSAGE_FILE_NOT_FOUND,
f"Cannot consume {self.path}: File not found."
)
def pre_check_directories(self):
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
os.makedirs(settings.THUMBNAIL_DIR, exist_ok=True)
os.makedirs(settings.ORIGINALS_DIR, exist_ok=True)
os.makedirs(settings.ARCHIVE_DIR, exist_ok=True)
def pre_check_duplicate(self):
with open(self.path, "rb") as f:
checksum = hashlib.md5(f.read()).hexdigest()
#if ReceipeImage.objects.filter(Q(checksum=checksum) | Q(archive_checksum=checksum)).exists(): # NOQA: E501
if ReceipeImage.objects.filter(checksum=checksum).exists():
if settings.CONSUMER_DELETE_DUPLICATES:
os.unlink(self.path)
self._fail(
MESSAGE_RECEIPE_ALREADY_EXISTS,
f"Not consuming {self.filename}: It is a duplicate."
)
def try_consume_file(self,
path,
applyBinarize=True,
debug=False,
task_id=None,
scannerFile=False):
"""
Return the receipe object if it was successfully created.
"""
self.path = path
self.filename = os.path.basename(path)
self.task_id = task_id or str(uuid.uuid4())
self._send_progress(0, 100, 'STARTING', MESSAGE_NEW_FILE)
# this is for grouping logging entries for this particular file
# together.
self.renew_logging_group()
# Make sure that preconditions for consuming the file are met.
self.pre_check_file_exists()
self.pre_check_directories()
self.pre_check_duplicate()
self.log("info", f"Consuming {self.filename}")
# Determine the parser class.
# Notify all listeners that we're going to do some work.
#document_consumption_started.send(
# sender=self.__class__,
# filename=self.path,
# logging_group=self.logging_group
#)
def progress_callback(current_progress, max_progress):
# recalculate progress to be within 20 and 80
p = int((current_progress / max_progress) * 50 + 20)
self._send_progress(p, 100, "WORKING")
# This doesn't parse the document yet, but gives us a parser.
print("info Create parser")
document_parser = ReceipeParser(self.logging_group, debug = debug)
self.log("debug", f"Parser: {type(document_parser).__name__}")
# Parse the document. This may take some time.
articles = None
date = None
market = None
#Crop and binarize image
if applyBinarize:
self.path_bin = self.path[:-4]+'_binarized_cropped.jpg'
if scannerFile:
crop_binarize_scanner(self.path, self.path_bin)
else:
crop_binarize(self.path, self.path_bin)
self.filename_bin = os.path.basename(self.path_bin)
else:
self.path_bin = self.path
self.filename_bin = os.path.basename(self.path)
self._send_progress(20, 100, 'WORKING', MESSAGE_PARSING_RECEIPE)
self.log("debug", "Parsing {}...".format(self.filename))
print("Start parsing...")
if scannerFile:
document_parser.parse(self.path_bin, self.filename_bin, source='scanner')
else:
document_parser.parse(self.path_bin, self.filename_bin, source='cam')
print("... done")
self.log("debug", f"Generating thumbnail for {self.filename}...")
self._send_progress(70, 100, 'WORKING',
MESSAGE_GENERATING_THUMBNAIL)
articles = document_parser.get_articles()
date = document_parser.get_date()
market = document_parser.get_market()
total = document_parser.get_total()
if debug:
#print(articles)
print(date)
print(market)
print(total)
self._send_progress(90, 100, 'WORKING',
MESSAGE_PARSE_DATE)
#archive_path = document_parser.get_archive_path()
# Prepare the document classifier.
self._send_progress(95, 100, 'WORKING', MESSAGE_SAVE_RECEIPE)
# now that everything is done, we can start to store the document
# in the system. This will be a transaction and reasonably fast.
if not debug:
try:
with transaction.atomic():
# store the receipe.
receipeImage = self._store(
articles=articles,
date=date,
market=market,
total=total
)
# If we get here, it was successful. Proceed with post-consume
# hooks. If they fail, nothing will get changed.
#document_consumption_finished.send(
# sender=self.__class__,
# document=document,
# logging_group=self.logging_group,
# classifier=classifier
#)
# After everything is in the database, copy the files into
# place. If this fails, we'll also rollback the transaction.
with FileLock(settings.MEDIA_LOCK):
create_source_path_directory(receipeImage.source_path)
self._write(self.path, receipeImage.source_path)
self._write(self.path_bin, receipeImage.source_path_trashed)
# Delete the file only if it was successfully consumed
self.log("debug", "Deleting file {}".format(self.path))
os.unlink(self.path)
self.log("debug", "Deleting file {}".format(self.path_bin))
os.unlink(self.path_bin)
# https://github.com/jonaswinkler/paperless-ng/discussions/1037
shadow_file = os.path.join(
os.path.dirname(self.path),
"._" + os.path.basename(self.path))
if os.path.isfile(shadow_file):
self.log("debug", "Deleting file {}".format(shadow_file))
os.unlink(shadow_file)
shadow_file = os.path.join(
os.path.dirname(self.path_bin),
"._" + os.path.basename(self.path_bin))
if os.path.isfile(shadow_file):
self.log("debug", "Deleting file {}".format(shadow_file))
os.unlink(shadow_file)
except Exception as e:
self._fail(
str(e),
f"The following error occured while consuming "
f"{self.filename}: {e}",
exc_info=True
)
finally:
pass
#document_parser.cleanup()
#self.run_post_consume_script(document)
self.log(
"info",
"Receipe {} consumption finished".format(receipeImage)
)
self._send_progress(100, 100, 'SUCCESS', MESSAGE_FINISHED, receipeImage.id)
return receipeImage
else:
return None
def _write(self,source, target):
with open(source, "rb") as read_file:
with open(target, "wb") as write_file:
write_file.write(read_file.read())
def _store(self, articles, date, market, total):
stats = os.stat(self.path)
self.log("debug", "Saving record to database")
created = date or timezone.make_aware(
datetime.datetime.fromtimestamp(stats.st_mtime))
#Save market if it not allready exists
market.save()
dateName=date or datetime.datetime.now()
try:
self.filename = ('Receipe_'+
str(uuid.uuid4())+
'_'+
dateName.strftime('%d-%m-%Y-%H-%M-%S')+
'.jpg'
)
self.filename_bin=self.filename[:-4]+'thrased.jpg'
except:
print('Something is wrong with new filename')
#Save receipeImage to database
with open(self.path, "rb") as f, open(self.path_bin, "rb") as fbin:
receipeImage = ReceipeImage.objects.create(
filename=self.filename,
filename_trashed=self.filename_bin,
checksum=hashlib.md5(f.read()).hexdigest(),
thrashed_checksum=hashlib.md5(fbin.read()).hexdigest(),
created=created,
modified=created,
)
receipeImage.save()
#Create new purchase
purchase = Purchase.objects.create(
purchase_date=dateName,
total_price=total,
market=market,
receipeImage=receipeImage
)
purchase.save()
for element in articles[1]:
if len(element.name) >= 50:
element.name = element.name[0:49]
if len(element.nameString) >= 50:
element.nameString = element.nameString[0:49]
article = Article.objects.create(
name=element.name
)
article.save()
purchaseArticle = PurchaseArticle.objects.create(
purchase_id=purchase,
article_id=article,
quantity=element.quantity,
price=element.price,
inSale=False
)
purchaseArticle.save()
try:
articleMaps = ArticleMaps.objects.create(
article=article,
receipeString=element.nameString,
location_x=element.nameBBox.x,
location_y=element.nameBBox.y,
location_h=element.nameBBox.h,
location_w=element.nameBBox.w,
receipeImage=receipeImage
)
except AttributeError:
articleMaps = ArticleMaps.objects.create(
article=article,
receipeString=element.nameString,
location_x=0,
location_y=0,
location_h=0,
location_w=0,
receipeImage=receipeImage
)
articleMaps.save()
for element in articles[0]:
print(element)
print(element.name)
print(element.articleId.pk)
article = Article.objects.get(
pk=element.articleId.pk
)
purchaseArticle = PurchaseArticle.objects.create(
purchase_id=purchase,
article_id=article,
quantity=element.quantity,
price=element.price,
inSale=False
)
purchaseArticle.save()
try:
articleMaps = ArticleMaps.objects.create(
article=article,
receipeString=element.nameString,
location_x=element.nameBBox.x,
location_y=element.nameBBox.y,
location_h=element.nameBBox.h,
location_w=element.nameBBox.w,
receipeImage=receipeImage
)
except AttributeError:
articleMaps = ArticleMaps.objects.create(
article=article,
receipeString=element.nameString,
location_x=0,
location_y=0,
location_h=0,
location_w=0,
receipeImage=receipeImage
)
articleMaps.save()
return receipeImage

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 30 13:50:33 2022
@author: elena
"""
import os
from django.conf import settings
def create_source_path_directory(source_path):
os.makedirs(os.path.dirname(source_path), exist_ok=True)

View File

@@ -0,0 +1,34 @@
from django.db.models.fields import Field
from django.db.models.lookups import In
from django.db.models import Lookup
from itertools import accumulate
@Field.register_lookup
class AncestorLevelsOf(In):
'''Find ancestors based on a level/path string in format of ie: 1_1_123_4'''
lookup_name = 'ancestorsof'
def get_prep_lookup(self):
'''
This function gets called before as_sql() and returns a value to be assigned as self.rhs.
Split the rhs input string by "_", and returns list of all possible ancestor paths.
'''
levels = self.rhs.split("_")
return list(accumulate(levels, func=lambda *i: "_".join(i)))
@Field.register_lookup
class SiblingLevelsOf(Lookup):
'''Find silbings based on a level/path string in format of ie: 1_1_123_4'''
lookup_name = 'siblingsof'
def get_prep_lookup(self):
'''Change the rhs to parent level'''
nodes = self.rhs.split("_")
return "^" + "_".join(nodes[:-1]) + "\_[^\_]+$"
def as_postgresql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return f"{lhs} ~* {rhs}", params

View File

@@ -0,0 +1,189 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun May 1 09:37:53 2022
@author: elena
"""
import cv2
import doxapy
import numpy as np
import scipy.ndimage as inter
class BBox:
def __init__(self, x, y, w, h):
self.x = x
self.y = y
self.w = w
self.h = h
def __repr__(self):
return "x: {:d}, y: {:d}, w: {:d}, h: {:d}".format(self.x,self.y,self.w,self.h)
def crop_binarize(inputfile: str, outputfile: str) -> None:
'''
Load inputfile from disk, apply binarization & threasholding and save as outputfile. Works best with pictures from a smartphone
Args:
inputfile (str): Filename of the colored picture with receipe
outputfile (str): Filename of the binarized & cropped picture
Returns:
None
'''
img = cv2.imread(inputfile)
# Rotate image
(H,W) = img.shape[:2]
if H < W:
img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
(H,W) = img.shape[:2]
# Save a copy of the image before we do the transformations
orig = img.copy()
#%% Start the cropping procedure
##(1) convert to hsv-space, then split the channels
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
##(2) threshold the S channel using adaptive method(`THRESH_OTSU`) or fixed thresh
th, threshed = cv2.threshold(s, 35, 255, cv2.THRESH_BINARY_INV)
#threshed = cv2.bitwise_not(threshed)
##(3) find all the external contours on the threshed S
cnts = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
canvas = img.copy()
## (4) sort and choose the largest contour
cnts = sorted(cnts, key = cv2.contourArea)
cnt = cnts[-1]
## (5) approx the contour, so the get the corner points
arclen = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02* arclen, True)
cv2.drawContours(canvas, [approx], -1, (0, 0, 255), 1, cv2.LINE_AA)
xcoords = approx[:,:,0]
ycoords = approx[:,:,1]
#rect = np.array([[max(xcoords),max(ycoords)],[min(xcoords),min(ycoords)],[min(xcoords),max(ycoords)],[max(xcoords),min(ycoords)]])
#cv2.drawContours(canvas, [rect], -1, (0, 255, 0), 1, cv2.LINE_AA)
## (6) Crop the original picture
crop = orig[int(min(ycoords)[0]):int(max(ycoords)[0]), int(min(xcoords)[0]):int(max(xcoords)[0])]
border = 30
(H,W) = crop.shape[:2]
crop = crop[:,border:W-border]
#%% Start the binarization
## (1) Convert to gray
crop = cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY)
## (2) Performing gaussian blur with following adaptive thresholding
crop = cv2.GaussianBlur(crop,(5,5),0)
thresh1 = cv2.adaptiveThreshold(crop,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.THRESH_BINARY,17,2)
#thresh1 = cv2.adaptiveThreshold(crop,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
# cv2.THRESH_BINARY,11,10)
#%% Apply improvements for text
# Applying dilation on the threshold image to get better letters, without interruptions
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 1))
erose = cv2.erode(cv2.bitwise_not(thresh1), rect_kernel, iterations = 1)
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (4, 4))
dilation = cv2.dilate(erose, rect_kernel, iterations = 1)
out = cv2.bitwise_not(dilation)
##% Remove some artefacts from the previous steps
# Mainly vertical lines
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,3))
dilate = cv2.dilate(cv2.bitwise_not(out), kernel, iterations=3)
edge = cv2.Canny(dilate, 100, 250)
cnts = cv2.findContours(edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
color = cv2.cvtColor(dilate, cv2.COLOR_GRAY2BGR)
badboxes = []
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
if h > 150:
a = BBox(x,y,w,h)
badboxes.append(a)
cv2.drawContours(color, [c], -1, (0, 255, 0), 3, cv2.LINE_AA)
cv2.rectangle(color,(x,y),(x+w,y+h),(255,0,0),3)
for element in badboxes:
out[element.y:element.y+element.h,element.x:element.x+element.w] = 255
cv2.imwrite(outputfile,out)
def correct_skew(image: np.array, delta:float = 0.1, limit: int = 2) -> (float, np.array):
'''
Here's an implementation of the Projection Profile Method algorithm for skew angle estimation.
Various angle points are projected into an accumulator array where the skew angle can be defined
as the angle of projection within a search interval that maximizes alignment. The idea is to
rotate the image at various angles and generate a histogram of pixels for each iteration. To
determine the skew angle, we compare the maximum difference between peaks and using this skew
angle, rotate the image to correct the skew.
Args:
image (np.array): image to apply skew corretion
delta (float): increment streps for angle
limit (int): maximal angle to test for
Returns:
best_angle (float): Calculated corretion angle
corrected (np.array): corrected image
'''
def determine_score(arr, angle):
data = inter.rotate(arr, angle, reshape=False, order=0)
histogram = np.sum(data, axis=1, dtype=float)
score = np.sum((histogram[1:] - histogram[:-1]) ** 2, dtype=float)
return histogram, score
scores = []
angles = np.arange(-limit, limit + delta, delta)
for angle in angles:
histogram, score = determine_score(image, angle)
scores.append(score)
best_angle = angles[scores.index(max(scores))]
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, best_angle, 1.0)
corrected = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, \
borderMode=cv2.BORDER_REPLICATE)
return best_angle, corrected
def crop_binarize_scanner(inputfile: str, outputfile: str) -> None:
'''
Load inputfile from disk, apply binarization & threasholding and save as outputfile. Works best with pictures from the scanner
Args:
inputfile (str): Filename of the colored picture with receipe
outputfile (str): Filename of the binarized & cropped picture
Returns:
None
'''
img = cv2.imread(inputfile, cv2.IMREAD_GRAYSCALE)
binary_image = np.empty(img.shape, img.dtype)
sauvola = doxapy.Binarization(doxapy.Binarization.Algorithms.ISAUVOLA)
sauvola.initialize(img)
sauvola.to_binary(binary_image, {"window": 45, "k": 0.009})
angle, out = correct_skew(binary_image)
print(angle)
cv2.imwrite(outputfile,out)

View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri May 27 13:10:59 2022
@author: elena
"""
import pandas as pd
import numpy as np
from .loggers import LoggingMixin
from django.conf import settings
class LinearAlignment(LoggingMixin):
logging_name = "receipeServer.alignment"
def __init__(self, logging_group, progress_callback=None):
super().__init__()
#self.logging_group = logging_group
self.FMatrix = []
self.distance = 0
self.compareString = ''
self.searchString = ''
#self.charIndex = {'A':0, 'B':1, 'C':2, 'D':3, 'E':4, 'F':5, 'G':6, 'H':7, 'I':8,
# 'J': 9, 'K': 10, 'L':11, 'M':12, 'N':13, 'O':14, 'P':15, 'Q':16, 'R':17,
# 'S': 18, 'T': 19, 'U':20, 'V':21, 'W':22, 'X':23, 'Y':24, 'Z':25,
# '1':26, '2': 27, '3': 28, '4':29, '5':30, '6':31, '7':32,
# '8':33, '9':34, '0':35, ':':36, '.': 37, '!':38, ' ':39, '%':40}
self.scoreMatrix = pd.read_csv(settings.BASE_DIR+'/receipe/score_matrix.csv',sep=';',index_col=0,header=0)
self.scoreMatrix.fillna(0,inplace=True)
def setStrings(self, compareString, searchString):
self.FMatrix = []
self.compareString = compareString
self.searchString = searchString
def scoring(self):
#Zeilen
compareStringSize = len(self.compareString)
line = []
for idx in range(0,compareStringSize + 1):
line.append(-self.distance*idx)
self.FMatrix.append(line)
#Spalten
for idy in range(1,len(self.searchString) + 1) :
for idx in range(0, compareStringSize):
if idx == 0:
line[idx] = -self.distance*idy
else:
line[idx] = 0
self.FMatrix.append(line)
for idy in range(1, len(self.FMatrix)):
for idx in range(1, len(self.FMatrix[0])):
elements = np.array([])
elements = np.append(elements,self.FMatrix[idy-1][idx-1] + self.scoreMatrix[self.searchString[idy-1]][self.compareString[idx-1]])
elements = np.append(elements,self.FMatrix[idy][idx-1] - self.distance);
elements = np.append(elements,self.FMatrix[idy-1][idx] - self.distance);
biggest = np.max(elements)
self.FMatrix[idy][idx] = biggest
return self.FMatrix[len(self.FMatrix)-1][len(self.FMatrix[0])-1] / np.max([len(self.compareString),len(self.searchString)]) / 10
def backTrace(self):
if len(self.FMatrix) == 0:
print('Run scoring before backTrace!')
return 0
alignmentA = "";
alignmentB = "";
idx = len(self.compareString)
idy = len(self.searchString)
while (idx > 0 or idy > 0) :
if (idx > 0) and (idy > 0) and (self.FMatrix[idy][idx] == self.FMatrix[idy-1][idx-1] + self.scoreMatrix[self.searchString[idy-1]][self.compareString[idx-1]]):
alignmentA = alignmentA + self.compareString[idx-1]
alignmentB = alignmentB + self.searchString[idy-1]
idx = idx - 1
idy = idy - 1
elif (idx > 0) and (self.FMatrix[idy][idx-1] - self.distance) :
alignmentA = alignmentA + self.compareString[idx-1]
alignmentB = alignmentB + '-'
idx = idx - 1
else:
alignmentA = alignmentA + '-'
alignmentB = alignmentB + self.searchString[idy-1]
idy = idy - 1
alignmentA = alignmentA[::-1]
alignmentB = alignmentB[::-1]
return alignmentA,alignmentB

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 30 13:14:41 2022
@author: elena
"""
import logging
import uuid
from django.conf import settings
class LoggingMixin:
logging_group = None
logging_name = None
def renew_logging_group(self):
self.logging_group = uuid.uuid4()
def log(self, level, message, **kwargs):
if self.logging_name:
logger = logging.getLogger(self.logging_name)
else:
name = ".".join([
self.__class__.__module__,
self.__class__.__name__
])
logger = logging.getLogger(name)
getattr(logger, level)(message, extra={
"group": self.logging_group
}, **kwargs)

View File

@@ -0,0 +1,295 @@
# Generated by Django 4.0.3 on 2022-06-11 14:05
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import receipe.models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Allergenes',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='allergene name')),
],
),
migrations.CreateModel(
name='Article',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='article name')),
('EAN', receipe.models.CharNullField(blank=True, max_length=50, null=True, unique=True, verbose_name='EAN')),
('offlink', models.URLField(blank=True, null=True, verbose_name='Open Food Fact link')),
('quantityPerPackage', models.PositiveIntegerField(blank=True, null=True, verbose_name='quantity per packages')),
('unit', receipe.models.CharNullField(blank=True, max_length=2, null=True, verbose_name='unit')),
('novaGroup', models.PositiveIntegerField(blank=True, null=True, verbose_name='NOVA group')),
('nutritionGrade', models.CharField(blank=True, max_length=1, null=True, verbose_name='nutri grade')),
('MwSt', models.PositiveIntegerField(blank=True, null=True, verbose_name='taxes')),
('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='created')),
('modified', models.DateTimeField(auto_now=True, db_index=True, verbose_name='modified')),
('nutritionImage', models.FilePathField(default=None, editable=False, help_text='Nutrition image filename in storage', max_length=1024, null=True, unique=True, verbose_name='nutriton image filepath')),
('ingredientsImage', models.FilePathField(default=None, editable=False, help_text='Ingredients image filename in storage', max_length=1024, null=True, unique=True, verbose_name='ingredients image filepath')),
('frontImage', models.FilePathField(default=None, editable=False, help_text='Front image filename in storage', max_length=1024, null=True, unique=True, verbose_name='front image filepath')),
('allergenes', models.ManyToManyField(blank=True, to='receipe.allergenes')),
],
),
migrations.CreateModel(
name='Brand',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='brand name')),
],
),
migrations.CreateModel(
name='EMBs',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='emb name')),
],
),
migrations.CreateModel(
name='Ingredients',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='ingredient name')),
],
),
migrations.CreateModel(
name='Labels',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='label name')),
],
),
migrations.CreateModel(
name='ManufacturingPlaces',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='manufacturing places name')),
],
),
migrations.CreateModel(
name='OriginIngredients',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='origin of igredients')),
],
),
migrations.CreateModel(
name='Packaging',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='packaging name')),
],
),
migrations.CreateModel(
name='ReceipeImage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('checksum', models.CharField(editable=False, help_text='The checksum of the original image.', max_length=32, unique=True, verbose_name='checksum')),
('thrashed_checksum', models.CharField(editable=False, help_text='The checksum of the thrashed image.', max_length=32, unique=True, verbose_name='threshed checksum')),
('filename', models.FilePathField(default=None, editable=False, help_text='Current filename in storage', max_length=1024, null=True, unique=True, verbose_name='filename')),
('filename_trashed', models.FilePathField(default=None, editable=False, help_text='Current filename of the modified picture in storage', max_length=1024, null=True, unique=True, verbose_name='filename trashed')),
('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='created')),
('modified', models.DateTimeField(auto_now=True, db_index=True, verbose_name='modified')),
('added', models.DateTimeField(db_index=True, default=django.utils.timezone.now, editable=False, verbose_name='added')),
],
),
migrations.CreateModel(
name='Traces',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='trace name')),
],
),
migrations.CreateModel(
name='NutritionalValues',
fields=[
('article', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='receipe.article')),
('energy', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='energy')),
('energyUnit', models.CharField(max_length=2)),
('fat', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='fat')),
('fatUnit', models.CharField(max_length=2)),
('saturatedFat', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='saturatedFat')),
('saturatedFatUnit', models.CharField(max_length=2)),
('carbohydrate', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='carbohydrate')),
('carbohydrateUnit', models.CharField(max_length=2)),
('sugars', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='sugars')),
('sugarsUnit', models.CharField(max_length=2)),
('dietaryFiber', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='dietaryFiber')),
('dietaryFiberUnit', models.CharField(max_length=2)),
('proteins', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='proteins')),
('proteinsUnit', models.CharField(max_length=2)),
('salt', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='salt')),
('saltUnit', models.CharField(max_length=2)),
('sodium', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='sodium')),
('sodiumUnit', models.CharField(max_length=2)),
('vitamin_a', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_a')),
('vitamin_aUnit', models.CharField(max_length=2)),
('vitamin_b1', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_b1')),
('vitamin_b1mUnit', models.CharField(max_length=2)),
('vitamin_b2', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_b2')),
('vitamin_b2Unit', models.CharField(max_length=2)),
('vitamin_b3', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_b3')),
('vitamin_b3Unit', models.CharField(max_length=2)),
('vitamin_b5', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_b5')),
('vitamin_b5Unit', models.CharField(max_length=2)),
('vitamin_b7', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_b7')),
('vitamin_b7Unit', models.CharField(max_length=2)),
('vitamin_b9', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_b9')),
('vitamin_b9Unit', models.CharField(max_length=2)),
('vitamin_b12', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_b12')),
('vitamin_b12Unit', models.CharField(max_length=2)),
('vitamin_c', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_c')),
('vitamin_cUnit', models.CharField(max_length=2)),
('vitamin_d', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_d')),
('vitamin_dUnit', models.CharField(max_length=2)),
('vitamin_e', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_e')),
('vitamin_eUnit', models.CharField(max_length=2)),
('vitamin_k', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='vitamin_k')),
('vitamin_kUnit', models.CharField(max_length=2)),
('potassium', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='potassium')),
('potassiumUnit', models.CharField(max_length=2)),
('calcium', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='calcium')),
('calciumUnit', models.CharField(max_length=2)),
('magnesium', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='magnesium')),
('magnesiumUnit', models.CharField(max_length=2)),
('iron', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='iron')),
('ironUnit', models.CharField(max_length=2)),
('zinc', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='zinc')),
('zincUnit', models.CharField(max_length=2)),
('sulfat', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='sulfat')),
('sulfatUnit', models.CharField(max_length=2)),
('chlorid', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='chlorid')),
('chloridUnit', models.CharField(max_length=2)),
('hydrogencarbonat', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='hydrogencarbonat')),
('hydrogencarbonatUnit', models.CharField(max_length=2)),
],
),
migrations.CreateModel(
name='Purchase',
fields=[
('purchase_date', models.DateField(verbose_name='purchase date')),
('payment_type', models.CharField(default='EC', max_length=10, verbose_name='payment type')),
('total_price', models.DecimalField(decimal_places=2, default=0, max_digits=7, verbose_name='total price')),
('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='created')),
('modified', models.DateTimeField(auto_now=True, db_index=True, verbose_name='modified')),
('receipeImage', models.OneToOneField(blank=True, on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='receipe.receipeimage')),
],
),
migrations.CreateModel(
name='PackagingArticle',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('weight', models.PositiveIntegerField(verbose_name='weight')),
('article_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='receipe.article', verbose_name='purchase id')),
('packaging_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='receipe.packaging', verbose_name='article id')),
],
),
migrations.CreateModel(
name='Market',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='market name')),
('street', models.CharField(max_length=50, verbose_name='street name')),
('street_number', models.PositiveIntegerField(verbose_name='street number')),
('zip_code', models.CharField(max_length=5, verbose_name='zip code')),
('city', models.CharField(max_length=50, verbose_name='city')),
('phone', receipe.models.CharNullField(blank=True, max_length=50, null=True, verbose_name='phone number')),
('created', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='created')),
('modified', models.DateTimeField(auto_now=True, db_index=True, verbose_name='modified')),
('articles', models.ManyToManyField(blank=True, to='receipe.article')),
],
),
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, unique=True, verbose_name='category name')),
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='receipe.category')),
],
),
migrations.CreateModel(
name='ArticleMaps',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('receipeString', models.CharField(max_length=50, verbose_name='receipe string')),
('location_x', models.PositiveIntegerField(verbose_name='x coordiante in image')),
('location_y', models.PositiveIntegerField(verbose_name='y coordiante in image')),
('location_h', models.PositiveIntegerField(verbose_name='height in image')),
('location_w', models.PositiveIntegerField(verbose_name='width in image')),
('article', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='receipe.article', verbose_name='article id')),
('receipeImage', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='receipe.receipeimage')),
],
),
migrations.AddField(
model_name='article',
name='brand',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='receipe.brand', verbose_name='brand id'),
),
migrations.AddField(
model_name='article',
name='category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='receipe.category', verbose_name='category id'),
),
migrations.AddField(
model_name='article',
name='embs',
field=models.ManyToManyField(blank=True, to='receipe.embs'),
),
migrations.AddField(
model_name='article',
name='ingredients',
field=models.ManyToManyField(blank=True, to='receipe.ingredients'),
),
migrations.AddField(
model_name='article',
name='manufacturingPlaces',
field=models.ManyToManyField(blank=True, to='receipe.manufacturingplaces'),
),
migrations.AddField(
model_name='article',
name='originIngredients',
field=models.ManyToManyField(blank=True, to='receipe.originingredients'),
),
migrations.AddField(
model_name='article',
name='packaging',
field=models.ManyToManyField(blank=True, through='receipe.PackagingArticle', to='receipe.packaging'),
),
migrations.AddField(
model_name='article',
name='traces',
field=models.ManyToManyField(blank=True, to='receipe.traces'),
),
migrations.CreateModel(
name='PurchaseArticle',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField(verbose_name='quantity')),
('net_weight', models.DecimalField(decimal_places=3, max_digits=7, verbose_name='net weight')),
('price', models.DecimalField(decimal_places=2, max_digits=7, verbose_name='price')),
('inSale', models.BooleanField(verbose_name='in sale')),
('article_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='receipe.article', verbose_name='article id')),
('purchase_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='receipe.purchase', verbose_name='purchase id')),
],
),
migrations.AddField(
model_name='purchase',
name='articles',
field=models.ManyToManyField(through='receipe.PurchaseArticle', to='receipe.article'),
),
migrations.AddField(
model_name='purchase',
name='market',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='receipe.market', verbose_name='market id'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.3 on 2022-06-11 15:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='purchasearticle',
name='inSale',
field=models.BooleanField(default=False, verbose_name='in sale'),
),
migrations.AlterField(
model_name='purchasearticle',
name='net_weight',
field=models.DecimalField(blank=True, decimal_places=3, max_digits=7, null=True, verbose_name='net weight'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2022-06-18 13:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0002_alter_purchasearticle_insale_and_more'),
]
operations = [
migrations.AddField(
model_name='article',
name='labels',
field=models.ManyToManyField(blank=True, to='receipe.labels'),
),
]

View File

@@ -0,0 +1,58 @@
# Generated by Django 4.0.3 on 2022-06-18 13:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0003_article_labels'),
]
operations = [
migrations.AlterField(
model_name='allergenes',
name='name',
field=models.CharField(max_length=50, unique=True, verbose_name='allergene name'),
),
migrations.AlterField(
model_name='brand',
name='name',
field=models.CharField(max_length=50, unique=True, verbose_name='brand name'),
),
migrations.AlterField(
model_name='embs',
name='name',
field=models.CharField(max_length=50, unique=True, verbose_name='emb name'),
),
migrations.AlterField(
model_name='ingredients',
name='name',
field=models.CharField(max_length=50, unique=True, verbose_name='ingredient name'),
),
migrations.AlterField(
model_name='labels',
name='name',
field=models.CharField(max_length=50, unique=True, verbose_name='label name'),
),
migrations.AlterField(
model_name='manufacturingplaces',
name='name',
field=models.CharField(max_length=50, unique=True, verbose_name='manufacturing places name'),
),
migrations.AlterField(
model_name='originingredients',
name='name',
field=models.CharField(max_length=50, unique=True, verbose_name='origin of igredients'),
),
migrations.AlterField(
model_name='packaging',
name='name',
field=models.CharField(max_length=50, unique=True, verbose_name='packaging name'),
),
migrations.AlterField(
model_name='traces',
name='name',
field=models.CharField(max_length=50, unique=True, verbose_name='trace name'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2022-06-19 16:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0004_alter_allergenes_name_alter_brand_name_and_more'),
]
operations = [
migrations.AddField(
model_name='article',
name='ecoGrade',
field=models.CharField(blank=True, max_length=1, null=True, verbose_name='eco grade'),
),
]

View File

@@ -0,0 +1,303 @@
# Generated by Django 4.0.3 on 2022-06-19 20:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0005_article_ecograde'),
]
operations = [
migrations.AlterField(
model_name='nutritionalvalues',
name='calcium',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='calcium'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='calciumUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='carbohydrate',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='carbohydrate'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='carbohydrateUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='chlorid',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='chlorid'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='chloridUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='dietaryFiber',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='dietaryFiber'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='dietaryFiberUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='energy',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='energy'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='energyUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='fat',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='fat'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='fatUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='hydrogencarbonat',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='hydrogencarbonat'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='hydrogencarbonatUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='iron',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='iron'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='ironUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='magnesium',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='magnesium'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='magnesiumUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='potassium',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='potassium'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='potassiumUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='proteins',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='proteins'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='proteinsUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='salt',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='salt'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='saltUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='saturatedFat',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='saturatedFat'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='saturatedFatUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='sodium',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='sodium'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='sodiumUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='sugars',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='sugars'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='sugarsUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='sulfat',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='sulfat'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='sulfatUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_a',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_a'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_aUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b1',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_b1'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b12',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_b12'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b12Unit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b1mUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b2',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_b2'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b2Unit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b3',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_b3'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b3Unit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b5',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_b5'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b5Unit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b7',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_b7'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b7Unit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b9',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_b9'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_b9Unit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_c',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_c'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_cUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_d',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_d'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_dUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_e',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_e'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_eUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_k',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='vitamin_k'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='vitamin_kUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='zinc',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True, verbose_name='zinc'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='zincUnit',
field=models.CharField(blank=True, max_length=2, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2022-06-20 16:14
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('receipe', '0006_alter_nutritionalvalues_calcium_and_more'),
]
operations = [
migrations.RenameField(
model_name='nutritionalvalues',
old_name='vitamin_b1mUnit',
new_name='vitamin_b1Unit',
),
]

View File

@@ -0,0 +1,42 @@
# Generated by Django 4.0.3 on 2022-07-09 12:54
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('receipe', '0007_rename_vitamin_b1munit_nutritionalvalues_vitamin_b1unit'),
]
operations = [
migrations.RemoveField(
model_name='category',
name='parent',
),
migrations.AddField(
model_name='category',
name='level',
field=models.CharField(default='0', max_length=100),
preserve_default=False,
),
migrations.AlterField(
model_name='packagingarticle',
name='article_id',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='receipe.article', verbose_name='article id'),
),
migrations.AlterField(
model_name='packagingarticle',
name='packaging_id',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='receipe.packaging', verbose_name='packaging id'),
),
migrations.AddIndex(
model_name='category',
index=models.Index(fields=['name'], name='receipe_cat_name_e79a8c_idx'),
),
migrations.AddIndex(
model_name='category',
index=models.Index(fields=['level'], name='receipe_cat_level_4234e7_idx'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2022-11-04 15:50
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('receipe', '0008_remove_category_parent_category_level_and_more'),
]
operations = [
migrations.RenameField(
model_name='article',
old_name='packaging',
new_name='packagings',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2023-02-23 14:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0009_rename_packaging_article_packagings'),
]
operations = [
migrations.AlterField(
model_name='nutritionalvalues',
name='energyUnit',
field=models.CharField(blank=True, max_length=4, null=True),
),
]

View File

@@ -0,0 +1,47 @@
# Generated by Django 4.0.3 on 2023-02-23 17:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0010_alter_nutritionalvalues_energyunit'),
]
operations = [
migrations.RemoveIndex(
model_name='category',
name='receipe_cat_name_e79a8c_idx',
),
migrations.RemoveIndex(
model_name='category',
name='receipe_cat_level_4234e7_idx',
),
migrations.RemoveField(
model_name='category',
name='level',
),
migrations.AddField(
model_name='category',
name='depth',
field=models.PositiveIntegerField(default=0),
preserve_default=False,
),
migrations.AddField(
model_name='category',
name='numchild',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='category',
name='path',
field=models.CharField(default='', max_length=255, unique=True),
preserve_default=False,
),
migrations.AlterField(
model_name='category',
name='name',
field=models.CharField(max_length=30),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2023-02-23 17:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0011_remove_category_receipe_cat_name_e79a8c_idx_and_more'),
]
operations = [
migrations.AlterField(
model_name='category',
name='name',
field=models.CharField(max_length=30, unique=True, verbose_name='category name'),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 4.0.3 on 2023-02-23 18:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('receipe', '0012_alter_category_name'),
]
operations = [
migrations.RemoveField(
model_name='article',
name='category',
),
migrations.DeleteModel(
name='Category',
),
]

View File

@@ -0,0 +1,32 @@
# Generated by Django 4.0.3 on 2023-02-23 18:22
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('receipe', '0013_remove_article_category_delete_category'),
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('path', models.CharField(max_length=255, unique=True)),
('depth', models.PositiveIntegerField()),
('numchild', models.PositiveIntegerField(default=0)),
('name', models.CharField(max_length=30, unique=True, verbose_name='category name')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='article',
name='category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='receipe.category', verbose_name='category id'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2023-02-24 16:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0014_category_article_category'),
]
operations = [
migrations.AlterField(
model_name='article',
name='frontImage',
field=models.ImageField(default=None, help_text='Front image', null=True, unique=True, upload_to='frontImages', verbose_name='front image'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.3 on 2023-02-25 12:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0015_alter_article_frontimage'),
]
operations = [
migrations.AlterField(
model_name='article',
name='ingredientsImage',
field=models.ImageField(default=None, help_text='Ingredients image', null=True, unique=True, upload_to='ingredientsImages', verbose_name='ingredients image filepath'),
),
migrations.AlterField(
model_name='article',
name='nutritionImage',
field=models.ImageField(default=None, help_text='Nutrition image', null=True, unique=True, upload_to='nutritionImages', verbose_name='nutriton image filepath'),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 4.0.3 on 2023-02-25 17:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('receipe', '0016_alter_article_ingredientsimage_and_more'),
]
operations = [
migrations.AlterField(
model_name='purchasearticle',
name='article_id',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='receipe.article', verbose_name='article id'),
),
migrations.AlterField(
model_name='purchasearticle',
name='purchase_id',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='receipe.purchase', verbose_name='purchase id'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.3 on 2023-03-01 08:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0017_alter_purchasearticle_article_id_and_more'),
]
operations = [
migrations.AlterField(
model_name='article',
name='frontImage',
field=models.ImageField(blank=True, default=None, help_text='Front image', null=True, unique=True, upload_to='frontImages', verbose_name='front image'),
),
migrations.AlterField(
model_name='article',
name='ingredientsImage',
field=models.ImageField(blank=True, default=None, help_text='Ingredients image', null=True, unique=True, upload_to='ingredientsImages', verbose_name='ingredients image filepath'),
),
migrations.AlterField(
model_name='article',
name='nutritionImage',
field=models.ImageField(blank=True, default=None, help_text='Nutrition image', null=True, unique=True, upload_to='nutritionImages', verbose_name='nutriton image filepath'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 4.0.3 on 2023-03-01 08:10
from django.db import migrations
import receipe.models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0018_alter_article_frontimage_and_more'),
]
operations = [
migrations.AlterField(
model_name='article',
name='frontImage',
field=receipe.models.ImageNullField(blank=True, default=None, help_text='Front image', null=True, unique=True, upload_to='frontImages', verbose_name='front image'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2023-03-01 08:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0019_alter_article_frontimage'),
]
operations = [
migrations.AlterField(
model_name='article',
name='frontImage',
field=models.ImageField(blank=True, default=None, help_text='Front image', null=True, unique=True, upload_to='frontImages', verbose_name='front image'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.3 on 2023-03-01 08:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0020_alter_article_frontimage'),
]
operations = [
migrations.AlterField(
model_name='article',
name='frontImage',
field=models.ImageField(blank=True, default=None, help_text='Front image', upload_to='frontImages', verbose_name='front image'),
),
migrations.AlterField(
model_name='article',
name='ingredientsImage',
field=models.ImageField(blank=True, default=None, help_text='Ingredients image', upload_to='ingredientsImages', verbose_name='ingredients image filepath'),
),
migrations.AlterField(
model_name='article',
name='nutritionImage',
field=models.ImageField(blank=True, default=None, help_text='Nutrition image', upload_to='nutritionImages', verbose_name='nutriton image filepath'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.3 on 2023-03-01 08:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0021_alter_article_frontimage_and_more'),
]
operations = [
migrations.AlterField(
model_name='article',
name='frontImage',
field=models.ImageField(blank=True, default=None, help_text='Front image', null=True, upload_to='frontImages', verbose_name='front image'),
),
migrations.AlterField(
model_name='article',
name='ingredientsImage',
field=models.ImageField(blank=True, default=None, help_text='Ingredients image', null=True, upload_to='ingredientsImages', verbose_name='ingredients image filepath'),
),
migrations.AlterField(
model_name='article',
name='nutritionImage',
field=models.ImageField(blank=True, default=None, help_text='Nutrition image', null=True, upload_to='nutritionImages', verbose_name='nutriton image filepath'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.3 on 2023-03-01 08:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0022_alter_article_frontimage_and_more'),
]
operations = [
migrations.AlterField(
model_name='article',
name='frontImage',
field=models.ImageField(blank=True, default=None, help_text='Front image', null=True, unique=True, upload_to='frontImages', verbose_name='front image'),
),
migrations.AlterField(
model_name='article',
name='ingredientsImage',
field=models.ImageField(blank=True, default=None, help_text='Ingredients image', null=True, unique=True, upload_to='ingredientsImages', verbose_name='ingredients image filepath'),
),
migrations.AlterField(
model_name='article',
name='nutritionImage',
field=models.ImageField(blank=True, default=None, help_text='Nutrition image', null=True, unique=True, upload_to='nutritionImages', verbose_name='nutriton image filepath'),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 4.0.3 on 2023-03-01 08:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('receipe', '0023_alter_article_frontimage_and_more'),
]
operations = [
migrations.RemoveField(
model_name='article',
name='frontImage',
),
migrations.RemoveField(
model_name='article',
name='ingredientsImage',
),
migrations.RemoveField(
model_name='article',
name='nutritionImage',
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.3 on 2023-03-01 08:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0024_remove_article_frontimage_and_more'),
]
operations = [
migrations.AddField(
model_name='article',
name='frontImage',
field=models.ImageField(blank=True, default=None, help_text='Front image', upload_to='frontImages', verbose_name='front image'),
),
migrations.AddField(
model_name='article',
name='ingredientsImage',
field=models.ImageField(blank=True, default=None, help_text='Ingredients image', upload_to='ingredientsImages', verbose_name='ingredients image filepath'),
),
migrations.AddField(
model_name='article',
name='nutritionImage',
field=models.ImageField(blank=True, default=None, help_text='Nutrition image', upload_to='nutritionImages', verbose_name='nutriton image filepath'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.3 on 2023-03-01 08:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0025_article_frontimage_article_ingredientsimage_and_more'),
]
operations = [
migrations.AlterField(
model_name='article',
name='frontImage',
field=models.ImageField(blank=True, default=None, help_text='Front image', null=True, upload_to='frontImages', verbose_name='front image'),
),
migrations.AlterField(
model_name='article',
name='ingredientsImage',
field=models.ImageField(blank=True, default=None, help_text='Ingredients image', null=True, upload_to='ingredientsImages', verbose_name='ingredients image filepath'),
),
migrations.AlterField(
model_name='article',
name='nutritionImage',
field=models.ImageField(blank=True, default=None, help_text='Nutrition image', null=True, upload_to='nutritionImages', verbose_name='nutriton image filepath'),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 4.0.3 on 2023-03-01 08:56
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('receipe', '0026_alter_article_frontimage_and_more'),
]
operations = [
migrations.RemoveField(
model_name='article',
name='frontImage',
),
migrations.RemoveField(
model_name='article',
name='ingredientsImage',
),
migrations.RemoveField(
model_name='article',
name='nutritionImage',
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.3 on 2023-03-01 08:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0027_remove_article_frontimage_and_more'),
]
operations = [
migrations.AddField(
model_name='article',
name='frontImage',
field=models.ImageField(blank=True, default=None, help_text='Front image', null=True, upload_to='frontImages', verbose_name='front image'),
),
migrations.AddField(
model_name='article',
name='ingredientsImage',
field=models.ImageField(blank=True, default=None, help_text='Ingredients image', null=True, upload_to='ingredientsImages', verbose_name='ingredients image filepath'),
),
migrations.AddField(
model_name='article',
name='nutritionImage',
field=models.ImageField(blank=True, default=None, help_text='Nutrition image', null=True, upload_to='nutritionImages', verbose_name='nutriton image filepath'),
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 4.0.3 on 2023-03-02 15:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0028_article_frontimage_article_ingredientsimage_and_more'),
]
operations = [
migrations.AlterField(
model_name='articlemaps',
name='location_h',
field=models.PositiveIntegerField(null=True, verbose_name='height in image'),
),
migrations.AlterField(
model_name='articlemaps',
name='location_w',
field=models.PositiveIntegerField(null=True, verbose_name='width in image'),
),
migrations.AlterField(
model_name='articlemaps',
name='location_x',
field=models.PositiveIntegerField(null=True, verbose_name='x coordiante in image'),
),
migrations.AlterField(
model_name='articlemaps',
name='location_y',
field=models.PositiveIntegerField(null=True, verbose_name='y coordiante in image'),
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 4.0.3 on 2023-03-03 14:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0029_alter_articlemaps_location_h_and_more'),
]
operations = [
migrations.AddField(
model_name='ingredients',
name='allergenes',
field=models.ManyToManyField(blank=True, to='receipe.allergenes'),
),
migrations.AddField(
model_name='ingredients',
name='subIngredients',
field=models.ManyToManyField(blank=True, to='receipe.ingredients'),
),
migrations.AddField(
model_name='ingredients',
name='vegan',
field=models.BooleanField(default=False, verbose_name='vegan?'),
),
migrations.AddField(
model_name='ingredients',
name='vegetarian',
field=models.BooleanField(default=False, verbose_name='vegetarian?'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2023-03-03 14:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0030_ingredients_allergenes_ingredients_subingredients_and_more'),
]
operations = [
migrations.AddField(
model_name='ingredients',
name='palmoilfree',
field=models.BooleanField(default=False, verbose_name='Palm oil free?'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2023-03-03 14:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0031_ingredients_palmoilfree'),
]
operations = [
migrations.AlterField(
model_name='ingredients',
name='palmoilfree',
field=models.BooleanField(default=True, verbose_name='Palm oil free?'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.3 on 2023-03-03 14:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0032_alter_ingredients_palmoilfree'),
]
operations = [
migrations.AddField(
model_name='ingredients',
name='additive',
field=models.BooleanField(blank=True, default=False, verbose_name='Additiv?'),
),
migrations.AddField(
model_name='ingredients',
name='eNumber',
field=models.CharField(max_length=5, null=True, verbose_name='E number'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.3 on 2023-03-03 14:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0033_ingredients_additive_ingredients_enumber'),
]
operations = [
migrations.AlterField(
model_name='ingredients',
name='additive',
field=models.BooleanField(default=False, verbose_name='Additiv?'),
),
migrations.AlterField(
model_name='ingredients',
name='eNumber',
field=models.CharField(blank=True, max_length=5, null=True, verbose_name='E number'),
),
]

View File

@@ -0,0 +1,36 @@
# Generated by Django 4.0.3 on 2023-03-03 15:21
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('receipe', '0034_alter_ingredients_additive_alter_ingredients_enumber'),
]
operations = [
migrations.CreateModel(
name='IngredientsArticle',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('position', models.PositiveIntegerField(verbose_name='Position')),
],
),
migrations.AlterField(
model_name='article',
name='ingredients',
field=models.ManyToManyField(blank=True, through='receipe.IngredientsArticle', to='receipe.ingredients'),
),
migrations.AddField(
model_name='ingredientsarticle',
name='article',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='receipe.article', verbose_name='article'),
),
migrations.AddField(
model_name='ingredientsarticle',
name='ingredient',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='receipe.ingredients', verbose_name='Ingredient'),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.0.3 on 2023-03-03 15:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('receipe', '0035_ingredientsarticle_alter_article_ingredients_and_more'),
]
operations = [
migrations.RemoveField(
model_name='article',
name='ingredients',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2023-03-03 15:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0036_remove_article_ingredients'),
]
operations = [
migrations.AddField(
model_name='article',
name='ingredients',
field=models.ManyToManyField(blank=True, through='receipe.IngredientsArticle', to='receipe.ingredients'),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 4.0.3 on 2023-03-03 15:25
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('receipe', '0037_article_ingredients'),
]
operations = [
migrations.RemoveField(
model_name='article',
name='ingredients',
),
migrations.DeleteModel(
name='IngredientsArticle',
),
]

View File

@@ -0,0 +1,36 @@
# Generated by Django 4.0.3 on 2023-03-03 15:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('receipe', '0038_remove_article_ingredients_delete_ingredientsarticle'),
]
operations = [
migrations.CreateModel(
name='IngredientsArticle',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('position', models.PositiveIntegerField(verbose_name='Position')),
],
),
migrations.AddField(
model_name='article',
name='ingredients',
field=models.ManyToManyField(blank=True, through='receipe.IngredientsArticle', to='receipe.ingredients'),
),
migrations.AddField(
model_name='ingredientsarticle',
name='article',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='receipe.article', verbose_name='article'),
),
migrations.AddField(
model_name='ingredientsarticle',
name='ingredient',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='receipe.ingredients', verbose_name='Ingredient'),
),
]

View File

@@ -0,0 +1,63 @@
# Generated by Django 4.0.3 on 2023-03-05 19:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0039_ingredientsarticle_article_ingredients_and_more'),
]
operations = [
migrations.AddField(
model_name='ingredientsarticle',
name='percent',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='Percentage on total weight'),
),
migrations.AlterField(
model_name='ingredients',
name='eNumber',
field=models.CharField(blank=True, max_length=7, null=True, verbose_name='E number'),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='carbohydrateUnit',
field=models.CharField(blank=True, default='g', max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='dietaryFiberUnit',
field=models.CharField(blank=True, default='g', max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='energyUnit',
field=models.CharField(blank=True, default='kJ', max_length=4, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='fatUnit',
field=models.CharField(blank=True, default='g', max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='proteinsUnit',
field=models.CharField(blank=True, default='g', max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='saltUnit',
field=models.CharField(blank=True, default='g', max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='saturatedFatUnit',
field=models.CharField(blank=True, default='g', max_length=2, null=True),
),
migrations.AlterField(
model_name='nutritionalvalues',
name='sugarsUnit',
field=models.CharField(blank=True, default='g', max_length=2, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2023-03-05 21:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0040_ingredientsarticle_percent_alter_ingredients_enumber_and_more'),
]
operations = [
migrations.AlterField(
model_name='purchasearticle',
name='price',
field=models.DecimalField(decimal_places=2, default=0, max_digits=7, verbose_name='price'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.3 on 2023-03-07 17:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('receipe', '0041_alter_purchasearticle_price'),
]
operations = [
migrations.RemoveField(
model_name='purchasearticle',
name='article_id',
),
migrations.AddField(
model_name='purchasearticle',
name='article',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='receipe.article', verbose_name='article object'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.3 on 2023-03-07 17:59
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('receipe', '0042_remove_purchasearticle_article_id_and_more'),
]
operations = [
migrations.RemoveField(
model_name='purchasearticle',
name='article',
),
migrations.AddField(
model_name='purchasearticle',
name='article_id',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='receipe.article', verbose_name='article id'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 4.0.3 on 2023-03-08 14:44
from django.db import migrations
import receipe.models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0043_remove_purchasearticle_article_and_more'),
]
operations = [
migrations.AlterField(
model_name='article',
name='unit',
field=receipe.models.CharNullField(blank=True, max_length=4, null=True, verbose_name='unit'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 4.0.3 on 2023-04-09 16:42
from django.db import migrations
import receipe.models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0044_alter_article_unit'),
]
operations = [
migrations.AlterField(
model_name='ingredients',
name='eNumber',
field=receipe.models.CharNullField(blank=True, max_length=7, null=True, verbose_name='E number'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2023-04-16 14:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0045_alter_ingredients_enumber'),
]
operations = [
migrations.AlterField(
model_name='ingredientsarticle',
name='percent',
field=models.FloatField(blank=True, default=None, null=True, verbose_name='Percentage on total weight'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2023-04-16 14:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('receipe', '0046_alter_ingredientsarticle_percent'),
]
operations = [
migrations.AlterField(
model_name='ingredients',
name='name',
field=models.CharField(max_length=100, unique=True, verbose_name='ingredient name'),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.2.1 on 2023-07-15 11:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("receipe", "0047_alter_ingredients_name"),
]
operations = [
migrations.AlterField(
model_name="ingredients",
name="name",
field=models.CharField(max_length=100, verbose_name="ingredient name"),
),
]

View File

@@ -0,0 +1,16 @@
# Generated by Django 4.2.1 on 2023-07-17 16:04
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("receipe", "0048_alter_ingredients_name"),
]
operations = [
migrations.AlterUniqueTogether(
name="articlemaps",
unique_together={("article", "receipeImage")},
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.2.1 on 2023-08-07 18:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("receipe", "0049_alter_articlemaps_unique_together"),
]
operations = [
migrations.AlterField(
model_name="article",
name="name",
field=models.CharField(max_length=100, verbose_name="article name"),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.2.1 on 2023-08-20 11:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("receipe", "0050_alter_article_name"),
]
operations = [
migrations.AddField(
model_name="purchase",
name="edit_finished",
field=models.BooleanField(default=True, verbose_name="edit finished"),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.2.1 on 2023-08-20 11:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("receipe", "0051_purchase_edit_finished"),
]
operations = [
migrations.AlterField(
model_name="purchase",
name="edit_finished",
field=models.BooleanField(default=False, verbose_name="edit finished"),
),
]

View File

@@ -0,0 +1,97 @@
# Generated by Django 4.2.1 on 2023-10-12 11:53
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("receipe", "0052_alter_purchase_edit_finished"),
]
operations = [
migrations.CreateModel(
name="Nutrient",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(
max_length=50, unique=True, verbose_name="nutrient name"
),
),
(
"isMacroNutrient",
models.BooleanField(
default=False, verbose_name="Is macronutrient?"
),
),
],
),
migrations.CreateModel(
name="NutrientArticle",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"value",
models.DecimalField(
blank=True,
decimal_places=2,
max_digits=7,
null=True,
verbose_name="value",
),
),
("unit", models.CharField(blank=True, max_length=4, null=True)),
(
"isEstimated",
models.BooleanField(
default=False, verbose_name="Is nutrient exstimated?"
),
),
(
"article",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="receipe.article",
verbose_name="article",
),
),
(
"nutrient",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="receipe.nutrient",
verbose_name="nutrient",
),
),
],
),
migrations.AddField(
model_name="article",
name="nutrients",
field=models.ManyToManyField(
blank=True, through="receipe.NutrientArticle", to="receipe.nutrient"
),
),
]

View File

@@ -0,0 +1,41 @@
# Generated by Django 4.2.1 on 2023-10-12 12:11
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("receipe", "0053_nutrient_nutrientarticle_article_nutrients"),
]
operations = [
migrations.CreateModel(
name="NutrientAlias",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(
max_length=50, unique=True, verbose_name="nutrient alias name"
),
),
(
"nutrient",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="receipe.nutrient",
verbose_name="Nutrient",
),
),
],
),
]

View File

@@ -0,0 +1,84 @@
# Generated by Django 4.2.1 on 2023-10-12 16:12
from django.db import migrations
def transfer_nutrivals(apps, schema_editor):
NutritionalValues = apps.get_model("receipe", "NutritionalValues")
Article = apps.get_model("receipe", "Article")
Nutrient = apps.get_model("receipe", "Nutrient")
NutrientArticle = apps.get_model("receipe", "NutrientArticle")
for nutriVal in NutritionalValues.objects.filter(energy__isnull=False):
keyVal = [['Energie','energy'],
['Fett','fat'],
['gesättigte Fettsäuren','saturatedFat'],
['Kohlenhydrate','carbohydrate'],
['Zucker','sugars'],
['Ballaststoffe','dietaryFiber'],
['Eiweiß','proteins'],
['Salz','salt'],
['Natrium','sodium'],
['Vitamin A','vitamin_a'],
['Vitamin B3','vitamin_b3'],
['Vitamin B1','vitamin_b1'],
['Vitamin B2','vitamin_b2'],
['Vitamin B5','vitamin_b5'],
#['Vitamin B6','vitamin_b6'],
['Vitamin B7','vitamin_b7'],
['Vitamin B9','vitamin_b9'],
['Vitamin B12','vitamin_b12'],
['Vitamin C','vitamin_c'],
['Vitamin D','vitamin_d'],
['Vitamin E','vitamin_e'],
['Vitamin K','vitamin_k'],
['Kalium','potassium'],
['Kalzium','calcium'],
['Magnesium','magnesium'],
['Eisen','iron'],
['Zink','zinc'],
['Sulfat','sulfat'],
['Chlorid','chlorid'],
['Hydrogencarbonat','hydrogencarbonat']
]
for element in keyVal:
if eval('nutriVal.'+element[1]) is not None:
if element[0] != 'Salz' or element[0] != 'Natrium':
nutrient = Nutrient.objects.get(name=element[0])
nutrienArticle = NutrientArticle(article=nutriVal.article,
nutrient=nutrient,
value=eval('nutriVal.'+element[1]),
unit=eval('nutriVal.'+element[1]+'Unit'),
isEstimated=False
)
nutrienArticle.save()
elif element[0] == 'Salz':
nutrient = Nutrient.objects.get(name=element[0])
nutrienArticle = NutrientArticle(article=nutriVal.article,
nutrient=nutrient,
value=eval('nutriVal.'+element[1]),
unit=eval('nutriVal.'+element[1]+'Unit'),
isEstimated=False
)
nutrienArticle.save()
nutrient = Nutrient.objects.get(name='Natrium')
nutrienArticle = NutrientArticle(article=nutriVal.article,
nutrient=nutrient,
value=eval('nutriVal.'+element[1])*0.3937,
unit=eval('nutriVal.'+element[1]+'Unit'),
isEstimated=False
)
nutrienArticle.save()
class Migration(migrations.Migration):
dependencies = [
("receipe", "0054_nutrientalias"),
]
operations = [
migrations.RunPython(transfer_nutrivals),
]

View File

@@ -0,0 +1,16 @@
# Generated by Django 4.2.1 on 2023-10-13 14:12
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("receipe", "0055_auto_20231012_1612"),
]
operations = [
migrations.AlterUniqueTogether(
name="nutrientarticle",
unique_together={("article", "nutrient")},
),
]

View File

@@ -0,0 +1,32 @@
# Generated by Django 4.2.1 on 2023-10-16 13:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("receipe", "0056_alter_nutrientarticle_unique_together"),
]
operations = [
migrations.CreateModel(
name="ReceipeString",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"receipeString",
models.CharField(
max_length=50, unique=True, verbose_name="receipe string"
),
),
],
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.1 on 2023-10-16 13:39
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("receipe", "0057_receipestring"),
]
operations = [
migrations.AddField(
model_name="articlemaps",
name="receipeStringRef",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="receipe.receipestring",
verbose_name="receipe string",
),
),
]

View File

@@ -0,0 +1,30 @@
# Generated by Django 4.2.1 on 2023-10-16 13:41
from django.db import migrations
def transfer_values(apps, schema_editor):
ReceipeString = apps.get_model("receipe", "ReceipeString")
ArticleMaps = apps.get_model("receipe", "ArticleMaps")
#Get all unique receipeStrings form ArticleMaps
receipeStrings = ArticleMaps.objects.values('receipeString').distinct()
#Add all unique receipeStrings to ReceipeString
for element in receipeStrings:
receipeString = ReceipeString(receipeString=element['receipeString'])
receipeString.save()
#For each receiepString in ArticleMaps, get the corresponding ReceipeString object and add it to ArticleMaps-receipeStringRef
for element in ArticleMaps.objects.all():
receipeString = ReceipeString.objects.get(receipeString=element.receipeString)
element.receipeStringRef = receipeString
element.save()
class Migration(migrations.Migration):
dependencies = [
("receipe", "0058_articlemaps_receipestringref"),
]
operations = [
migrations.RunPython(transfer_values),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 4.2.1 on 2023-10-16 15:02
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("receipe", "0059_auto_20231016_1341"),
]
operations = [
migrations.RemoveField(
model_name="articlemaps",
name="receipeString",
),
migrations.RenameField(
model_name='articlemaps',
old_name='receipeStringRef',
new_name='receipeString',
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.2.1 on 2023-10-16 15:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("receipe", "0060_remove_articlemaps_receipestringref_and_more"),
]
operations = [
migrations.RenameField(
model_name="receipestring",
old_name="receipeString",
new_name="name",
),
]

View File

@@ -0,0 +1,11 @@
# Generated by Django 4.2.1 on 2023-10-19 12:07
from django.db import migrations
from django.contrib.postgres.operations import TrigramExtension
class Migration(migrations.Migration):
dependencies = [
("receipe", "0061_rename_receipestring_receipestring_name"),
]
operations = [TrigramExtension()]

View File

@@ -0,0 +1,633 @@
from django.db import models
from django.utils import timezone
from django.conf import settings
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from django.db.utils import IntegrityError
from .hierarchicalData import AncestorLevelsOf, SiblingLevelsOf
from treebeard.mp_tree import MP_Node
import os
# Create your models here.
class CharNullField(models.CharField):
"""
Subclass of the CharField that allows empty strings to be stored as NULL.
This class is used to set charfield be optional but unique when added.
Set blank=True, null=True when declaring the field
"""
description = "CharField that stores NULL but returns ''."
def get_prep_value(self, value):
"""
Catches value right before sending to db.
"""
if value == '': # If Django tries to save an empty string, send the db None (NULL).
return None
else: # Otherwise, just pass the value.
return value
def from_db_value(self, value, expression, connection):
"""
Gets value right out of the db and changes it if its ``None``.
"""
if value is None:
return ''
else:
return value
def to_python(self, value):
"""
Gets value right out of the db or an instance, and changes it if its ``None``.
"""
if isinstance(value, models.CharField): # If an instance, just return the instance.
return value
if value is None: # If db has NULL, convert it to ''.
return ''
return value # Otherwise, just return the value.
class ImageNullField(models.ImageField):
"""
Subclass of the CharField that allows empty strings to be stored as NULL.
This class is used to set charfield be optional but unique when added.
Set blank=True, null=True when declaring the field
"""
description = "ImageField that stores NULL but returns ''."
def get_prep_value(self, value):
"""
Catches value right before sending to db.
"""
if value == '': # If Django tries to save an empty string, send the db None (NULL).
return None
else: # Otherwise, just pass the value.
return value
def from_db_value(self, value, expression, connection):
"""
Gets value right out of the db and changes it if its ``None``.
"""
if value is None:
return ''
else:
return value
def to_python(self, value):
"""
Gets value right out of the db or an instance, and changes it if its ``None``.
"""
if isinstance(value, models.ImageField): # If an instance, just return the instance.
return value
if value is None: # If db has NULL, convert it to ''.
return ''
return value # Otherwise, just return the value.
class ReceipeImage(models.Model):
#RowId = models.IntegerField(primary_key=True)
checksum = models.CharField(
"checksum",
max_length=32,
editable=False,
unique=True,
help_text="The checksum of the original image."
)
thrashed_checksum = models.CharField(
"threshed checksum",
max_length=32,
editable=False,
unique=True,
help_text="The checksum of the thrashed image."
)
filename = models.FilePathField(
"filename",
max_length=1024,
editable=False,
default=None,
unique=True,
null=True,
help_text="Current filename in storage"
)
filename_trashed = models.FilePathField(
"filename trashed",
max_length=1024,
editable=False,
default=None,
unique=True,
null=True,
help_text="Current filename of the modified picture in storage"
)
created = models.DateTimeField(
"created",
default=timezone.now, db_index=True)
modified = models.DateTimeField(
"modified",
auto_now=True, editable=False, db_index=True)
added = models.DateTimeField(
"added",
default=timezone.now, editable=False, db_index=True)
@property
def source_path(self):
fname = str(self.filename)
return os.path.join(
settings.ORIGINALS_DIR,
fname
)
@property
def source_path_trashed(self):
fname = str(self.filename_trashed)
return os.path.join(
settings.ORIGINALS_DIR,
fname
)
@property
def source_file(self):
return open(self.source_path, "rb")
@property
def source_file_trashed(self):
return open(self.source_path_trashed, "rb")
class Allergenes(models.Model):
#RowId = models.IntegerField(primary_key=True)
name = models.CharField("allergene name", max_length=50, unique=True)
def __str__(self):
return 'PK: '+str(self.pk) + ' ' + self.name
class Brand(models.Model):
#RowId = models.IntegerField(primary_key=True)
name = models.CharField("brand name", max_length=50, unique=True)
def __str__(self):
return 'PK: '+str(self.pk) + ' ' + self.name
class Packaging(models.Model):
#RowId = models.IntegerField(primary_key=True)
name = models.CharField("packaging name", max_length=50, unique=True)
def __str__(self):
return 'PK: '+str(self.pk) + ' ' + self.name
class Ingredients(models.Model):
#RowId = models.IntegerField(primary_key=True)
name = models.CharField("ingredient name", max_length=100)
allergenes = models.ManyToManyField(Allergenes, blank=True)
vegan = models.BooleanField("vegan?",default=False)
vegetarian = models.BooleanField("vegetarian?",default=False)
palmoilfree = models.BooleanField("Palm oil free?",default=True)
additive = models.BooleanField("Additiv?",default=False)
eNumber = CharNullField("E number", max_length=7,null=True, blank=True)
subIngredients = models.ManyToManyField('self', blank=True, symmetrical=False)
#class Meta:
# unique_together = ('name', 'subIngredients',)
def __unicode__(self):
return "Ingredients: "+str(self.id)+" "+self.name
def __str__(self):
return 'PK: '+str(self.pk) + ' ' + self.name
@receiver(m2m_changed, sender=Ingredients.subIngredients.through)
def verify_uniqueness(sender, **kwargs):
ingredient = kwargs.get('instance', None)
action = kwargs.get('action', None)
subIngredients = kwargs.get('pk_set', None)
if action == 'pre_add':
for subIngredient in subIngredients:
if Ingredients.objects.filter(name=ingredient.name).filter(subIngredients=subIngredient):
raise IntegrityError('Ingredient with name %s already exists for subIngredient %s' % (ingredient.name, Ingredients.objects.get(pk=subIngredient)))
class Traces(models.Model):
#RowId = models.IntegerField(primary_key=True)
name = models.CharField("trace name", max_length=50, unique=True)
class Labels(models.Model):
#RowId = models.IntegerField(primary_key=True)
name = models.CharField("label name", max_length=50, unique=True)
class EMBs(models.Model):
#RowId = models.IntegerField(primary_key=True)
name = models.CharField("emb name", max_length=50, unique=True)
class ManufacturingPlaces(models.Model):
#RowId = models.IntegerField(primary_key=True)
name = models.CharField("manufacturing places name", max_length=50, unique=True)
class OriginIngredients(models.Model):
#RowId = models.IntegerField(primary_key=True)
name = models.CharField("origin of igredients", max_length=50, unique=True)
class Category(MP_Node):
name = models.CharField("category name",max_length=30,unique=True)
node_order_by = ['name']
def __str__(self):
return 'Category: {}'.format(self.name)
class Nutrient(models.Model):
name = models.CharField("nutrient name", max_length=50, unique=True)
isMacroNutrient = models.BooleanField("Is macronutrient?", default=False)
def __str__(self):
return 'PK: '+str(self.pk) + ' ' + self.name
class NutrientAlias(models.Model):
nutrient = models.ForeignKey(
Nutrient,
on_delete=models.CASCADE,
verbose_name="Nutrient"
)
name = models.CharField("nutrient alias name", max_length=50, unique=True)
def __str__(self):
return 'PK: '+str(self.pk) + ' ' + self.name
#-------------------------------------------------------------------------------
class Article(models.Model):
#RowId = models.IntegerField(primary_key=True)
name = models.CharField("article name", max_length=100)
brand = models.ForeignKey(
Brand,
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="brand id"
)
EAN = CharNullField(
"EAN", max_length=50,
unique=True,
blank=True,
null=True
)
offlink = models.URLField("Open Food Fact link", blank=True, null=True)
category = models.ForeignKey(
Category,
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="category id"
)
quantityPerPackage = models.PositiveIntegerField("quantity per packages", blank=True, null=True)
unit = CharNullField("unit",max_length=4, blank=True, null=True)
novaGroup = models.PositiveIntegerField("NOVA group", blank=True, null=True)
nutritionGrade = models.CharField("nutri grade",max_length=1, blank=True, null=True)
ecoGrade = models.CharField("eco grade",max_length=1, blank=True, null=True)
MwSt = models.PositiveIntegerField("taxes", blank=True, null=True)
allergenes = models.ManyToManyField(Allergenes, blank=True)
ingredients = models.ManyToManyField(Ingredients, through='IngredientsArticle', blank=True)
traces = models.ManyToManyField(Traces, blank=True)
embs = models.ManyToManyField(EMBs, blank=True)
manufacturingPlaces = models.ManyToManyField(ManufacturingPlaces, blank=True)
originIngredients = models.ManyToManyField(OriginIngredients, blank=True)
packagings = models.ManyToManyField(Packaging, through='PackagingArticle', blank=True)
nutrients = models.ManyToManyField(Nutrient, through='NutrientArticle', blank=True)
labels = models.ManyToManyField(Labels, blank=True)
models.ForeignKey(
Category,
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="category id"
)
created = models.DateTimeField(
"created",
default=timezone.now,
db_index=True)
modified = models.DateTimeField(
"modified",
auto_now=True,
editable=False,
db_index=True)
nutritionImage = models.ImageField(
"nutriton image filepath",
default=None,
#unique=True,
blank=True,
null=True,
help_text="Nutrition image",
upload_to='nutritionImages'
)
ingredientsImage = models.ImageField(
"ingredients image filepath",
default=None,
#unique=True,
blank=True,
null=True,
help_text="Ingredients image",
upload_to='ingredientsImages'
)
frontImage = models.ImageField(
"front image",
default=None,
#unique=True,
null=True,
blank=True,
help_text="Front image",
upload_to='frontImages'
)
def __str__(self):
return 'PK: '+str(self.pk) + ' ' + self.name
#-------------------------------------------------------------------------------
class IngredientsArticle(models.Model):
ingredient = models.ForeignKey(
Ingredients,
blank=True,
null=True,
on_delete=models.CASCADE,
verbose_name="Ingredient"
)
article = models.ForeignKey(
Article,
blank=True,
null=True,
on_delete=models.CASCADE,
verbose_name="article"
)
position = models.PositiveIntegerField("Position")
percent = models.FloatField(
"Percentage on total weight",
blank=True,
null=True,
default=None
)
#-------------------------------------------------------------------------------
class PackagingArticle(models.Model):
packaging_id = models.ForeignKey(
Packaging,
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="packaging id"
)
article_id = models.ForeignKey(
Article,
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="article id"
)
weight = models.PositiveIntegerField("weight")
#-------------------------------------------------------------------------------
class NutrientArticle(models.Model):
nutrient = models.ForeignKey(
Nutrient,
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="nutrient"
)
article = models.ForeignKey(
Article,
blank=True,
null=True,
on_delete=models.CASCADE,
verbose_name="article"
)
value = models.DecimalField("value", max_digits=7, decimal_places=2, blank=True, null=True)
unit = models.CharField(max_length=4, blank=True, null=True)
isEstimated = models.BooleanField("Is nutrient exstimated?", default=False)
class Meta:
unique_together = ('article', 'nutrient',)
# Deprectated, will be deleted later
class NutritionalValues(models.Model):
article = models.OneToOneField(
Article,
on_delete=models.CASCADE,
primary_key=True,
)
energy = models.DecimalField("energy",max_digits=7,decimal_places=2,blank=True, null=True)
energyUnit = models.CharField(max_length=4,blank=True, null=True,default='kJ')
fat = models.DecimalField("fat",max_digits=7,decimal_places=2,blank=True, null=True)
fatUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
saturatedFat = models.DecimalField("saturatedFat",max_digits=7,decimal_places=2,blank=True, null=True)
saturatedFatUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
carbohydrate = models.DecimalField("carbohydrate",max_digits=7,decimal_places=2,blank=True, null=True)
carbohydrateUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
sugars = models.DecimalField("sugars",max_digits=7,decimal_places=2,blank=True, null=True)
sugarsUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
dietaryFiber = models.DecimalField("dietaryFiber",max_digits=7,decimal_places=2,blank=True, null=True)
dietaryFiberUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
proteins = models.DecimalField("proteins",max_digits=7,decimal_places=2,blank=True, null=True)
proteinsUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
salt = models.DecimalField("salt",max_digits=7,decimal_places=2,blank=True, null=True)
saltUnit = models.CharField(max_length=2,blank=True, null=True,default='g')
sodium = models.DecimalField("sodium",max_digits=7,decimal_places=2,blank=True, null=True)
sodiumUnit = models.CharField(max_length=2,blank=True, null=True)
vitamin_a = models.DecimalField("vitamin_a",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_aUnit = models.CharField(max_length=2,blank=True, null=True)
vitamin_b1 = models.DecimalField("vitamin_b1",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_b1Unit = models.CharField(max_length=2,blank=True, null=True)
vitamin_b2 = models.DecimalField("vitamin_b2",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_b2Unit = models.CharField(max_length=2,blank=True, null=True)
vitamin_b3 = models.DecimalField("vitamin_b3",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_b3Unit = models.CharField(max_length=2,blank=True, null=True)
vitamin_b5 = models.DecimalField("vitamin_b5",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_b5Unit = models.CharField(max_length=2,blank=True, null=True)
vitamin_b7 = models.DecimalField("vitamin_b7",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_b7Unit = models.CharField(max_length=2,blank=True, null=True)
vitamin_b9 = models.DecimalField("vitamin_b9",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_b9Unit = models.CharField(max_length=2,blank=True, null=True)
vitamin_b12 = models.DecimalField("vitamin_b12",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_b12Unit = models.CharField(max_length=2,blank=True, null=True)
vitamin_c = models.DecimalField("vitamin_c",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_cUnit = models.CharField(max_length=2,blank=True, null=True)
vitamin_d = models.DecimalField("vitamin_d",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_dUnit = models.CharField(max_length=2,blank=True, null=True)
vitamin_e = models.DecimalField("vitamin_e",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_eUnit = models.CharField(max_length=2,blank=True, null=True)
vitamin_k = models.DecimalField("vitamin_k",max_digits=7,decimal_places=2,blank=True, null=True)
vitamin_kUnit = models.CharField(max_length=2,blank=True, null=True)
potassium = models.DecimalField("potassium",max_digits=7,decimal_places=2,blank=True, null=True)
potassiumUnit = models.CharField(max_length=2,blank=True, null=True)
calcium = models.DecimalField("calcium",max_digits=7,decimal_places=2,blank=True, null=True)
calciumUnit = models.CharField(max_length=2,blank=True, null=True)
magnesium = models.DecimalField("magnesium",max_digits=7,decimal_places=2,blank=True, null=True)
magnesiumUnit = models.CharField(max_length=2,blank=True, null=True)
iron = models.DecimalField("iron",max_digits=7,decimal_places=2,blank=True, null=True)
ironUnit = models.CharField(max_length=2,blank=True, null=True)
zinc = models.DecimalField("zinc",max_digits=7,decimal_places=2,blank=True, null=True)
zincUnit = models.CharField(max_length=2,blank=True, null=True)
sulfat = models.DecimalField("sulfat",max_digits=7,decimal_places=2,blank=True, null=True)
sulfatUnit = models.CharField(max_length=2,blank=True, null=True)
chlorid = models.DecimalField("chlorid",max_digits=7,decimal_places=2,blank=True, null=True)
chloridUnit = models.CharField(max_length=2,blank=True, null=True)
hydrogencarbonat = models.DecimalField("hydrogencarbonat",max_digits=7,decimal_places=2,blank=True, null=True)
hydrogencarbonatUnit = models.CharField(max_length=2,blank=True, null=True)
class ReceipeString(models.Model):
name = models.CharField("receipe string", max_length=50, unique=True)
def __str__(self):
return 'PK: '+str(self.pk) + ' ' + self.receipeString
class ArticleMaps(models.Model):
article = models.ForeignKey(
Article,
blank=True,
null=True,
on_delete=models.CASCADE,
verbose_name="article id"
)
#receipeString = models.CharField("receipe string", max_length=50)
receipeString = models.ForeignKey(
ReceipeString,
on_delete=models.CASCADE,
null=True,
verbose_name="receipe string"
)
location_x = models.PositiveIntegerField("x coordiante in image", null=True)
location_y = models.PositiveIntegerField("y coordiante in image", null=True)
location_h = models.PositiveIntegerField("height in image", null=True)
location_w = models.PositiveIntegerField("width in image", null=True)
receipeImage = models.ForeignKey(ReceipeImage, on_delete=models.CASCADE)
class Meta:
unique_together = ('article', 'receipeImage',)
def __str__(self):
return 'PK: '+str(self.pk) + ' ' + ' ' + self.receipeString.name
class Market(models.Model):
#RowId = models.AutoField(primary_key=True)
name = models.CharField("market name", max_length=50)
street = models.CharField("street name", max_length=50)
street_number = models.PositiveIntegerField("street number")
zip_code = models.CharField("zip code", max_length=5)
city = models.CharField("city", max_length=50)
phone = CharNullField("phone number",max_length=50, blank=True, null=True)
articles = models.ManyToManyField(Article,blank=True)
created = models.DateTimeField(
"created",
default=timezone.now,
db_index=True)
modified = models.DateTimeField(
"modified",
auto_now=True,
editable=False,
db_index=True)
def __str__(self):
return 'PK: '+str(self.pk) + ' ' + self.name + ' ' + self.street + ' ' + str(self.street_number) + ' ' + self.city
class Purchase(models.Model):
purchase_date = models.DateField("purchase date")
payment_type = models.CharField("payment type",max_length=10,default='EC')
total_price = models.DecimalField("total price",max_digits=7,decimal_places=2,default=0)
articles = models.ManyToManyField(Article, through='PurchaseArticle')
market = models.ForeignKey(
Market,
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="market id"
)
created = models.DateTimeField(
"created",
default=timezone.now,
db_index=True)
modified = models.DateTimeField(
"modified",
auto_now=True,
editable=False,
db_index=True)
receipeImage = models.OneToOneField(
ReceipeImage,
on_delete=models.CASCADE,
primary_key=True,
blank = True,
)
edit_finished = models.BooleanField("edit finished", default=False)
def __str__(self):
return 'PK: '+str(self.pk) + ' Datum: ' + self.purchase_date.strftime("%m/%d/%Y") + ' Preis: ' +str(self.total_price)
#-------------------------------------------------------------------------------
class PurchaseArticle(models.Model):
#shoud be renamed to article, but migrations will delete the current database
article_id = models.ForeignKey(
Article,
blank=True,
null=True,
on_delete=models.CASCADE,
verbose_name="article id"
)
purchase_id = models.ForeignKey(
Purchase,
blank=True,
null=True,
on_delete=models.CASCADE,
verbose_name="purchase id"
)
quantity = models.PositiveIntegerField("quantity")
net_weight = models.DecimalField("net weight",max_digits=7,decimal_places=3, blank=True, null=True)
price = models.DecimalField("price",max_digits=7,decimal_places=2,default=0)
inSale = models.BooleanField("in sale",default=False)

View File

@@ -0,0 +1,743 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 30 22:47:15 2022
@author: elena
"""
#import logging
import cv2
import re
import os
from copy import deepcopy
import numpy as np
import pytesseract
from scipy import stats
from ordered_set import OrderedSet
from .loggers import LoggingMixin
from .image_processing import BBox
from .classes import noDBArticle
#from .models import ReceipeString, Market
import receipe.models as models
from .linearalignment import LinearAlignment
from django.conf import settings
from django.utils import timezone
from django.contrib.postgres.search import TrigramSimilarity
# This regular expression will try to find dates in the document at
# hand and will match the following formats:
# - XX.YY.ZZZZ with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - XX/YY/ZZZZ with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - XX-YY-ZZZZ with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - ZZZZ.XX.YY with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - ZZZZ/XX/YY with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - ZZZZ-XX-YY with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - XX. MONTH ZZZZ with XX being 1 or 2 and ZZZZ being 2 or 4 digits
# - MONTH ZZZZ, with ZZZZ being 4 digits
# - MONTH XX, ZZZZ with XX being 1 or 2 and ZZZZ being 4 digits
DATE_REGEX = re.compile(
r'(\b|(?!=([_-])))([0-9]{1,2})[\.\/-]([0-9]{1,2})[\.\/-]([0-9]{4}|[0-9]{2})(\b|(?=([_-])))|' # NOQA: E501
r'(\b|(?!=([_-])))([0-9]{4}|[0-9]{2})[\.\/-]([0-9]{1,2})[\.\/-]([0-9]{1,2})(\b|(?=([_-])))|' # NOQA: E501
r'(\b|(?!=([_-])))([0-9]{1,2}[\. ]+[^ ]{3,9} ([0-9]{4}|[0-9]{2}))(\b|(?=([_-])))|' # NOQA: E501
r'(\b|(?!=([_-])))([^\W\d_]{3,9} [0-9]{1,2}, ([0-9]{4}))(\b|(?=([_-])))|'
r'(\b|(?!=([_-])))([^\W\d_]{3,9} [0-9]{4})(\b|(?=([_-])))'
)
STREET_REGEX = re.compile(r'[A-Z][a-z]{1,} \d{1,3}')
ZIP_REGEX = re.compile(r'\d{5} .*') #Valid for Germany
PHONE_REGEX = re.compile(r'\d{4,}(\/|-| \/ | - )\d*')
FLOAT_REGEX = re.compile(r'(|-|\+)\d*(,|\.)\d*')
MULTIPLE_REGEX = re.compile(r'\d{1,}(x|X){1,}')
class ReceipeParser(LoggingMixin):
logging_name = "receipeServer.parsing"
def __init__(self, logging_group, debug=False, progress_callback=None):
super().__init__()
self.logging_group = logging_group
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
self.debug = debug
self.archive_path = None
self.date = None
self.market = None
self.articles = None
self.total = None
self.progress_callback = progress_callback
self.knownArticles = OrderedSet([])
self.unknownArticles = set([])
def getLine(self,boxList: list) -> list:
"""Take a list of bounding boxes (with text) and combine them to (text) lines
Args:
boxList (list): The list containing all bounding boxes
Returns:
lines (list): a list of (text) lines (one bouning box per line)
"""
lines = []
idx = 0
while idx < len(boxList):
line = [boxList[idx]]
yref1 = boxList[idx].y
yref2 = yref1 + boxList[idx].h
idy = idx + 1
#Iterate over all boxes
while idy < len(boxList):
yval1 = boxList[idy].y
yval2 = boxList[idy].y + boxList[idy].h
#Calculate the length of overlapp between the two boxes
l = np.abs(np.max([yval1,yref1]) - np.min([yval2,yref2]))
#If distance combared to box 1 and box 2 is more than 50% and there is some overlapp include the box
if l/boxList[idx].h > 0.5 and l/boxList[idy].h > 0.5 and not (yval2 < yref1 or yval1 > yref2):
line.append(boxList[idy])
idx = idx + 1
idy = idy + 1
else:
idy = idy + 1
line.sort(key=lambda s: s.x)
lines.append(line)
idx = idx + 1
return lines
def grouper(self, iterable: list, interval: int = 2) -> list:
"""Group iterable into lines, within interval height. I do not understand what the code does
Args:
iterable (list): The list containing all (character) bounding boxes
interval (int): Measure to decide if boxes should be included in line
Returns:
group (generator): returns a generator for the lines
"""
prev = None
group = []
for item in iterable:
if not prev or abs(item[1] - prev[1]) <= interval:
group.append(item)
else:
yield group
group = [item]
prev = item
if group:
yield group
def doOCR(self,imgwidth: int, rois: list, lines: list, h: float, border: int = 10) -> list:
"""For the given rois (image parts) apply OCR and return the corresponding text
Args:
imgwidth (int): Width of the total image
rois (list): List containing the image fragments
lines (list): List with the bounding boxes
h (float): Mean lineheigth
border: Padding to apply around the bounding box
Returns:
texts (list): a list of the text of the different lines
"""
texts = []
#Tesseract
for idy in range(0,len(rois)):
lineroi = rois[idy]
linebox = lines[idy]
tmptext = []
for idx in range(len(lineroi)):
lineimg = np.ones((h+2*border,imgwidth), np.uint8)*255
lineimg[border:border+h,linebox[idx].x:linebox[idx].x+linebox[idx].w][:] = cv2.resize(lineroi[idx], (linebox[idx].w,h))
tmptext.append(pytesseract.image_to_string(lineimg,lang='deu').strip())
if tmptext != '':
texts.append(tmptext)
print(texts)
return texts
def parse_date(self,text: str):
"""
Returns the date of the receipe.
"""
def __parser(ds, date_order):
"""
Call dateparser.parse with a particular date ordering
"""
import dateparser
return dateparser.parse(
ds,
settings={
"DATE_ORDER": date_order,
"PREFER_DAY_OF_MONTH": "first",
"RETURN_AS_TIMEZONE_AWARE":
True
}
)
def __filter(date):
if date and date.year > 1900 and \
date <= timezone.now():
return date
return None
date = None
# Iterate through all regex matches in text and try to parse the date
for m in re.finditer(DATE_REGEX, text):
date_string = m.group(0)
try:
date = __parser(date_string, settings.DATE_ORDER)
except (TypeError, ValueError):
# Skip all matches that do not parse to a proper date
continue
date = __filter(date)
if date is not None:
break
return date
def parse_zip_city(self,text):
zipCode = None
city = None
for m in re.findall(ZIP_REGEX, text):
stringElements = m.split(' ')
zipCode = stringElements[0]
if len(stringElements) > 1:
city = stringElements[1]
return zipCode, city
def parse_street_streetNum(self,text):
street = None
streetNum = None
for m in re.findall(STREET_REGEX, text):
stringElements = m.split(' ')
street = stringElements[0]
if len(stringElements) > 1:
streetNum = stringElements[1]
return street, streetNum
def parse_phone(self,text):
for m in re.finditer(PHONE_REGEX, text):
return m.group(0)
def parse_float(self,text):
for m in re.finditer(FLOAT_REGEX, text):
value = m.group(0)
value = value.replace(',','.')
try:
value = float(value)
except ValueError:
value = 0
return value
return 0
def extractMarket(self,lineText,allText):
'''Extract market detail out of the quarter of lines (allow for some artefacts from logo)
'''
tmpMarket = models.Market()
for line in lineText:
if line[0] != '':
tmpMarket.name = line[0]
break
roi = ''
try:
for idx in range(0,int(len(lineText)/4)):
roi = roi + lineText[idx][0] +'\n'
except IndexError:
roi = ''
try:
tmpMarket.zipCode, tmpMarket.city = self.parse_zip_city(roi)
except TypeError:
try:
tmpMarket.zipCode, tmpMarket.city = self.parse_zip_city(allText)
except TypeError:
tmpMarket.zipCode = ''
tmpMarket.city = ''
try:
tmpMarket.street, tmpMarket.street_number = self.parse_street_streetNum(roi)
except TypeError:
try:
tmpMarket.street, tmpMarket.street_number = self.parse_street_streetNum(allText)
except TypeError:
tmpMarket.street = ''
tmpMarket.street_number = 0
try:
tmpMarket.phone = self.parse_phone(roi)
except TypeError:
try:
tmpMarket.phone = self.parse_phone(allText)
except TypeError:
tmpMarket.phone = ''
#TODO: Try some fuzzy search
#markets = Market.objects.filter( name=tmpMarket.name, street=tmpMarket.street)
# Trigram search for name and street
markets = models.Market.objects.annotate(similarity=TrigramSimilarity('name', tmpMarket.name)).filter(similarity__gt=0.3).order_by("-similarity")
markets = markets.annotate(similarity=TrigramSimilarity('street', tmpMarket.street)).filter(similarity__gt=0.3).order_by("-similarity")
if len(markets) != 0:
return markets[0]
else:
return tmpMarket
def progress(self, current_progress, max_progress):
if self.progress_callback:
self.progress_callback(current_progress, max_progress)
def fixString(self,oldString):
newString = oldString.replace('Ä','A').replace('Ö','O').replace('Ü','U').replace('ä','a').replace('ö','o').replace('ü','u').replace('#','').replace('©','o').replace('','').replace('*','').replace('',',')
return newString
def get_date(self):
return self.date
def get_market(self):
return self.market
def get_articles(self):
return [self.knownArticles, self.unknownArticles]
def get_total(self):
return self.total
def lineSegmentationSimple(self, img: np.array) -> list:
#%% Dilate in y direction to detect text lines
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (75,2))
dilate = cv2.dilate(cv2.bitwise_not(img), kernel, iterations=1)
# Find contours and filter using aspect ratio
# Remove non-text contours by filling in the contour
edge = cv2.Canny(dilate, 100, 250)
cnts = cv2.findContours(edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
xall = []
yall = []
wall = []
hall = []
textBoxes = []
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
xall.append(x)
yall.append(y)
wall.append(w)
hall.append(h)
zscoreH = stats.zscore(hall)
for idx in range(0,len(xall)):
#ar = w / float(h)
#if hall[idx] > lowerHbound:# and hall[idx] < upperHbound:
if zscoreH[idx] > 0.1 and zscoreH[idx] < 1.4 and hall[idx] < wall[idx]:
tmp = BBox(xall[idx],yall[idx],wall[idx],hall[idx])
textBoxes.append(tmp)
#cv2.drawContours(color, [cnts[idx]], -1, (0, 255, 0), 3, cv2.LINE_AA)
lines = self.getLine(textBoxes)
lines.reverse()
return lines
def lineSegmentationMSER(self, img: np.array) -> list:
mser = cv2.MSER_create()
regions, bboxes = mser.detectRegions(img)
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions]
bboxes_list = list()
heights = list()
for hull in hulls:
x, y, w, h = cv2.boundingRect(hull)
bboxes_list.append([x, y, x + w, y + h]) # Create list of bounding boxes, with each bbox containing the left-top and right-bottom coordinates
heights.append(h)
heights = sorted(heights) # Sort heights
median_height = heights[int(len(heights) / 2)] / 3 # Find third of the median height
#print(median_height)
bboxes_list = sorted(bboxes_list, key=lambda k: k[1]) # Sort the bounding boxes based on y1 coordinate ( y of the left-top coordinate )
combined_bboxes = self.grouper(bboxes_list, median_height) # Group the bounding boxes
lines = []
for group in combined_bboxes:
x_min = min(group, key=lambda k: k[0])[0] # Find min of x1
x_max = max(group, key=lambda k: k[2])[2] # Find max of x2
y_min = min(group, key=lambda k: k[1])[1] # Find min of y1
y_max = max(group, key=lambda k: k[3])[3] # Find max of y2
if abs(y_min - y_max) < 3 * 3 * median_height and abs(y_min - y_max) > median_height and abs(x_min - x_max) > 100:
#cv2.rectangle(img, (x_min, y_min), (x_max, y_max), (0, 255, 0), 2)
lines.append(BBox(x_min,y_min,x_max-x_min,y_max-y_min))
linesWithSplit = []
for element in lines:
edges = cv2.Canny(img[element.y:element.y+element.h,element.x:element.x+element.w],100,200)
#imgplot = plt.imshow(edges)
#Calculate projection onto x axis
proj_v = np.sum(edges,0)
#plt.plot(proj_v)
#plt.show()
count = 0
prev = 0
indexend = 0
zeroSequence = []
gaplist = []
#Find all gaps (projected value of 0)
for i in range(0,len(proj_v)):
if proj_v[i] == 0:
count += 1
else:
if count > prev:
prev = count
indexend = i
if count > 70:
gaplist.append([prev,indexend-prev,indexend-1])
#indexend = 0
count = 0
prev = 0
indexend = 0
#print("The longest sequence of 0's is "+str(prev))
#print("index start at: "+ str(indexend-prev))
#print("index ends at: "+ str(indexend-1))
#Split the line at the borders of the gaps
if len (gaplist) < 1:
linesWithSplit.append([element])
elif len (gaplist) == 1:
tmpList = []
tmpList.append( BBox(element.x,element.y,gaplist[0][1],element.h) )
tmpList.append( BBox(element.x+gaplist[0][2],element.y,element.w-gaplist[0][2],element.h) )
linesWithSplit.append(tmpList)
else:
tmpList = []
for i in range(0,len(gaplist)):
if i == 0:
tmpList.append( BBox(element.x,element.y,gaplist[i][1],element.h) )
elif i < len(gaplist):
tmpList.append( BBox(element.x+gaplist[i-1][2],element.y,gaplist[i][1]-gaplist[i-1][2],element.h) )
tmpList.append( BBox(element.x+gaplist[-1][2],element.y,element.w-gaplist[-1][2],element.h) )
linesWithSplit.append(tmpList)
return linesWithSplit
def parse(self,path,inputfile,source='cam'):
self.knownArticles = set([])
self.unknownArticles = set([])
# Read image from which text needs to be extracted
print(path)
img = cv2.imread(path,cv2.IMREAD_GRAYSCALE)
if source == 'cam':
lines = self.lineSegmentationSimple(img)
elif source == 'scanner':
lines = self.lineSegmentationMSER(img)
rois = []
hsum = 0
numh = 0
for idx in range(0,len(lines)):
line = lines[idx]
tmproi = []
for element in line:
hsum = hsum + element.h
numh = numh + 1
#cv2.rectangle(color,(element.x,element.y),(element.x+element.w,element.y+element.h),(255,0,0),3)
tmproi.append(img[element.y:element.y+element.h,element.x:element.x+element.w])
rois.append(tmproi)
hmean = hsum/numh
h = int(hmean)
#newimg = np.ones(((h+border)*len(rois)+2*border,len(img[0,:])), np.uint8)*255
lineText = self.doOCR(len(img[0,:]), rois, lines, h)
allText = pytesseract.image_to_string(img,lang='deu')
#%% Extract market
self.market = self.extractMarket(lineText,allText)
print(self.market.name)
print(self.market.street)
print(self.market.street_number)
print(self.market.zip_code)
print(self.market.city)
print(self.market.phone)
#%% Extract date
date = None
for element in lineText:
date = self.parse_date(' '.join(element))
if date != None:
break
if date == None:
#Try again with OCR of the whole receipe
date = self.parse_date(allText)
self.date = date
print(self.date)
#%% Extract total
self.total = 0
for line in lineText:
text = ' '.join(line)
if 'EUR' in text or 'SUMME' in text or 'TOTAL' in text:
value = self.parse_float(text)
if value != 0:
self.total = value
break
# If we find no result, try again with the total text
if self.total == 0:
for line in allText.split('\n'):
text = line
if 'EUR' in text or 'SUMME' in text or 'TOTAL' in text:
value = self.parse_float(text)
if value != 0:
self.total = value
break
print(self.total)
#%% Extract articles
self.articles = []
newArticle = noDBArticle()
newArticle.quantity = 0
for idx in range(5,len(lineText)):
print(lineText[idx])
text = ' '.join(lineText[idx])
#The last import line of a receipe ends with the total or sum of prices, so afterwards we stop.
if ('EUR' in text or 'SUMME' in text or 'TOTAL' in text) and len(self.articles) > 1:
break
elif ('EUR' in text or 'SUMME' in text or 'TOTAL' in text) and len(self.articles) == 0:
continue
if newArticle.quantity <= 1:
newArticle.quantity = 1
#Runs only if regex found.
for m in re.finditer(MULTIPLE_REGEX, text.replace(' ','').replace('\t','')):
string = m.group(0)
for k in re.finditer(r'\d{1,}', string):
newArticle.quantity = int(k.group(0))
#TODO: Extract price per unit from this line
#If multiple articles are found, there is no info on the article, so we skip the rest
if newArticle.quantity > 1:
continue
if len(lineText[idx]) > 1:
newArticle.name = lineText[idx][0]
newArticle.nameString = lineText[idx][0]
newArticle.nameBBox = lines[idx][0]
#for element in lineText[idx]:
for idy in range(0,len(lineText[idx])):
try:
newArticle.price = self.parse_float(lineText[idx][idy]) / newArticle.quantity
except ZeroDivisionError:
print(newArticle.quantity)
newArticle.price = 0.01
if newArticle.price != 0:
newArticle.priceString = lineText[idx][idy]
newArticle.priceBBox = lines[idx][idy]
break
newArticle.name = self.fixString(newArticle.name)
copyOfNewArticle = deepcopy(newArticle)
self.articles.append(copyOfNewArticle)
newArticle.name=''
newArticle.quantity = 0
savedArticleMaps = models.ReceipeString.objects.all()
alignmendObject = LinearAlignment(self.logging_group)
for parsedArticle in self.articles:
matches = models.Article.objects.annotate(similarity=TrigramSimilarity('name', parsedArticle.name)).filter(similarity__gt=0.3).order_by("-similarity")
print(parsedArticle.name)
print(matches)
if len(matches) > 0:
# We try to find out, if we already have added the same article
tmpknownArticles = self.knownArticles.copy()
sizeSet = len(tmpknownArticles)
tmpknownArticles.add(matches[0])
# If the size remain the same, then the article is alread in our list, so we have to increase the quantity by 1 and then add it
if len(tmpknownArticles) == sizeSet:
# Elements of a set are not accessible directly, so we have to iterate over all elements
for element in self.knownArticles:
if element.name == article.name and element.id == article.id:
# For the comparision of two elements, the quantity does not count, so we remove it first
self.knownArticles.remove(matches[0])
article.quantity = article.quantity + 1
break
# Then we add same again, but with changed quantity.
self.knownArticles.add(matches[0])
# Otherwise, we just add it
else:
self.knownArticles.add(matches[0])
else:
tmpunknownArticles = self.unknownArticles.copy()
sizeSet = len(tmpunknownArticles)
tmpunknownArticles.add(parsedArticle)
# If the size remain the same, then the article is alread in our list, so we have to increase the quantity by 1 and then add it
if len(tmpunknownArticles) == sizeSet:
# Elements of a set are not accessible directly, so we have to iterate over all elements
for element in self.unknownArticles:
if element.name == parsedArticle.name:
parsedArticle.quantity = parsedArticle.quantity + 1
break
# For the comparision of to elements, the quantity does not count, so we remove it first
try:
self.unknownArticles.remove(parsedArticle)
except KeyError:
pass
# Then we add same again, but with changed quantity.
self.unknownArticles.add(parsedArticle)
# Otherwise, we just add it
else:
self.unknownArticles.add(parsedArticle)
'''
for parsedArticle in self.articles:
possibleMatches = []
possibleMatchesScore = np.array([])
for articleMaps in savedArticleMaps:
#print(parsedArticle.name)
#print(articleMaps.receipeString)
#TODO: Add lower case letter to alignment matrix
alignmendObject.setStrings(parsedArticle.name.upper(), articleMaps.receipeString.upper())
try:
stringScore = alignmendObject.scoring()
except KeyError:
stringScore = 0
if stringScore > 0.75:
print(parsedArticle.name.upper()+' '+articleMaps.receipeString.upper()+' Score: '+str(stringScore))
possibleMatches.append([parsedArticle,articleMaps])
possibleMatchesScore = np.append(possibleMatchesScore, stringScore)
if stringScore == 1:
break
if len(possibleMatches) > 0:
maxIdx = np.argmax(possibleMatchesScore)
# Take the article with best matching name
# First entry of list is the parsedArticle, then we overwrite name and id with the known one from the DB
article = possibleMatches[maxIdx][0]
article.name = possibleMatches[maxIdx][1].receipeString
article.id = possibleMatches[maxIdx][1].pk
article.articleId = possibleMatches[maxIdx][1].article
# We try to find out, if we already have added the same article
tmpknownArticles = self.knownArticles.copy()
sizeSet = len(tmpknownArticles)
tmpknownArticles.add(article)
# If the size remain the same, then the article is alread in our list, so we have to increase the quantity by 1 and then add it
if len(tmpknownArticles) == sizeSet:
# Elements of a set are not accessible directly, so we have to iterate over all elements
for element in self.knownArticles:
if element.name == article.name and element.id == article.id:
# For the comparision of two elements, the quantity does not count, so we remove it first
self.knownArticles.remove(article)
article.quantity = article.quantity + 1
break
# Then we add same again, but with changed quantity.
self.knownArticles.add(article)
# Otherwise, we just add it
else:
self.knownArticles.add(article)
else:
tmpunknownArticles = self.unknownArticles.copy()
sizeSet = len(tmpunknownArticles)
tmpunknownArticles.add(parsedArticle)
# If the size remain the same, then the article is alread in our list, so we have to increase the quantity by 1 and then add it
if len(tmpunknownArticles) == sizeSet:
# Elements of a set are not accessible directly, so we have to iterate over all elements
for element in self.unknownArticles:
if element.name == parsedArticle.name:
parsedArticle.quantity = parsedArticle.quantity + 1
break
# For the comparision of to elements, the quantity does not count, so we remove it first
try:
self.unknownArticles.remove(parsedArticle)
except KeyError:
pass
# Then we add same again, but with changed quantity.
self.unknownArticles.add(parsedArticle)
# Otherwise, we just add it
else:
self.unknownArticles.add(parsedArticle)
'''

View File

@@ -0,0 +1,45 @@
;A;B;C;D;E;F;G;H;I;J;K;L;M;N;O;P;Q;R;S;T;U;V;W;X;Y;Z;1;2;3;4;5;6;7;8;9;0;,;.;!; ;%;-;{;}
A;10;4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
B;4;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8;;;;;;;;;;
C;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
D;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
E;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
F;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
G;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
H;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I;;;;;;;;;10;7;;;;;;;;;;;;;;;;;8;;;;;;;;;;;;8;;;;;
J;;;;;;;;;7;10;;5;;;;;;;;;;;;;;;;;;;;;;;;;;;7;;;;;
K;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
L;;;;;;;;;;5;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
M;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
N;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
O;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;8;;;;;;;;
P;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Q;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;;
R;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;;
S;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;;
T;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;;
U;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;;
V;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;;
W;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;;
X;;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;;
Y;;;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;;
Z;;;;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;;;
1;;;;;;;;;8;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;8;;;;;;
2;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;;
3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;;
4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;;
5;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;;
6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;;
7;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;;
8;;8;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;;
9;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;;
0;;;;;;;;;;;;;;8;;;;;;;;;;;;;;;;;;;;;;10;;;;;;;;
,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;8;;;;;;
.;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8;10;;;;;;
!;;;;;;;;;8;7;;;;;;;;;;;;;;;;;8;;;;;;;;;;;;10;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;;;;
%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;;;
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;;
{;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10;
};;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10
1 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 1 2 3 4 5 6 7 8 9 0 , . ! % - { }
2 A 10 4
3 B 4 10 8
4 C 10
5 D 10
6 E 10
7 F 10
8 G 10
9 H 10
10 I 10 7 8 8
11 J 7 10 5 7
12 K 10
13 L 5 10
14 M 10
15 N 10
16 O 10 8
17 P 10
18 Q 10
19 R 10
20 S 10
21 T 10
22 U 10
23 V 10
24 W 10
25 X 10
26 Y 10
27 Z 10
28 1 8 10 8
29 2 10
30 3 10
31 4 10
32 5 10
33 6 10
34 7 10
35 8 8 10
36 9 10
37 0 8 10
38 , 10 8
39 . 8 10
40 ! 8 7 8 10
41 10
42 % 10
43 - 10
44 { 10
45 } 10

View File

@@ -0,0 +1,837 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Apr 24 14:32:15 2022
@author: elena
"""
from django.db.utils import IntegrityError
from rest_framework import serializers
from .models import *
class MarketSerializer(serializers.ModelSerializer):
id = serializers.SerializerMethodField('pk')
class Meta:
model = Market
fields = ('id','name', 'street', 'street_number', 'zip_code', 'city', 'phone','articles','created','modified')
def pk(self, obj):
return obj.pk
class ReceipeImageSerializer(serializers.ModelSerializer):
id = serializers.SerializerMethodField('pk')
class Meta:
model = Market
fields = ('id','pk')
def pk(self, obj):
return obj.pk
class PurchaseSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
purchase_date = serializers.DateField()
payment_type = serializers.CharField(max_length=20)
total_price = serializers.DecimalField(max_digits=7,decimal_places=2)
market = MarketSerializer()
edit_finished = serializers.BooleanField()
receipeImage = ReceipeImageSerializer()
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `Purchase` instance, given the validated data.
"""
try:
return Purchase.objects.create(**validated_data)
except IntegrityError:
return Purchase.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `Purchase` instance, given the validated data.
"""
instance.purchase_date = validated_data.get('purchase_date', instance.purchase_date)
instance.save()
return instance
'''
class PurchaseSerializer(serializers.ModelSerializer):
id = serializers.SerializerMethodField('pk')
class Meta:
model = Purchase
fields = ('id','purchase_date', 'payment_type', 'total_price', 'articles', 'market', 'created','modified','receipeImage','edit_finished')
depth = 0
def pk(self, obj):
return obj.pk
'''
class PurchaseSerializerDepth(serializers.ModelSerializer):
id = serializers.SerializerMethodField('pk')
class Meta:
model = Purchase
fields = ('id','purchase_date', 'payment_type', 'total_price', 'articles', 'market', 'created','modified','receipeImage','edit_finished')
depth = 1
def pk(self, obj):
return obj.pk
class NutritionalValuesSerializer(serializers.ModelSerializer):
id = serializers.SerializerMethodField('pk')
class Meta:
model = NutritionalValues
fields = '__all__'
def pk(self, obj):
return obj.pk
class ReceipeStringSerializer(serializers.ModelSerializer):
id = serializers.SerializerMethodField('pk')
class Meta:
model = ReceipeString
fields = '__all__'
def pk(self, obj):
return obj.pk
class ArticleMapSerzializer(serializers.ModelSerializer):
class Meta:
model = ArticleMaps
fields = ('pk','receipeString','location_x','location_y','location_h','location_w')
depth = 1
class CategorySerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=30)
path = serializers.CharField(max_length=255)
depth = serializers.IntegerField()
numchild = serializers.IntegerField()
ancestorIds = serializers.SerializerMethodField()
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `Category` instance, given the validated data.
"""
try:
return Category.objects.create(**validated_data)
except IntegrityError:
return Category.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `Category` instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
# For visualisation we need the ids of all ancestors, in order to expand the tree. Therefore we fetch the data here
def get_ancestorIds(self, obj):
ancestors = obj.get_ancestors()
ids = []
for element in ancestors:
ids.append(element.id)
return ids
class AllergenesSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=50)
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `Allergenes` instance, given the validated data.
"""
try:
return Allergenes.objects.create(**validated_data)
except IntegrityError:
return Allergenes.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `Allergenes` instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
class TracesSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=50)
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `Traces` instance, given the validated data.
"""
try:
return Traces.objects.create(**validated_data)
except IntegrityError:
return Traces.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `Traces` instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
class RecursiveField(serializers.Serializer):
def to_representation(self, value):
serializer = self.parent.parent.__class__(value, context=self.context)
return serializer.data
class IngredientsSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=100)
allergenes = AllergenesSerializer(allow_null = True, many=True)
vegan = serializers.BooleanField(allow_null = True)
vegetarian = serializers.BooleanField(allow_null = True)
palmoilfree = serializers.BooleanField(allow_null = True)
additive = serializers.BooleanField(allow_null = True)
eNumber = serializers.CharField(max_length=7, allow_null = True, allow_blank=True)
subIngredients = RecursiveField(allow_null= True, many=True)#IngredientsSerializer(allow_null = True, many=True) #serializers.ListField()
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `Ingredients` instance, given the validated data.
"""
print("Ingredients:Create")
#print(validated_data)
allergenes = validated_data.pop('allergenes')
subIngredients = validated_data.pop('subIngredients')
print(subIngredients)
allergenes_list = []
subIngredients_list = []
for allergene in allergenes:
allergenes_list.append(Allergenes.objects.get(name=allergene['name']))
for subIngredient in subIngredients:
print(subIngredient)
subIngredients_list.append(Ingredients.objects.get(name=subIngredient['name']))
print(allergenes_list)
try:
newObject = Ingredients.objects.create(**validated_data)
newObject.allergenes.set(allergenes_list)
newObject.subIngredients.set(subIngredients_list)
newObject.save()
print(newObject)
return newObject
except IntegrityError:
return Ingredients.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `Ingredients` instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
# Validate if subIngredients are given, if not raise error. This is needed, because value is emtpy, therefore we have to access the inital data
def validate_subIngredients(self, value):
print("Ingredients:Validate subIngredients")
print(value)
print(self.context)
#print(self.initial_data)
#print(self.initial_data['subIngredients'])
if not 'subIngredients' in self.context:
self.context['subIngredients'] = []
return self.context['subIngredients']
else:
return self.context['subIngredients']
#IngredientsSerializer.base_fields['subIngredients'] = IngredientsSerializer(allow_null = True, many=True)
class LabelsSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=50)
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `Brand` instance, given the validated data.
"""
print("Labels:Create")
try:
return Labels.objects.create(**validated_data)
except IntegrityError:
return Labels.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
print("Labels:Update")
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
class PackagingSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=50)
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `Packaging` instance, given the validated data.
"""
print("Packaging:Create")
try:
return Packaging.objects.create(**validated_data)
except IntegrityError:
return Packaging.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `Packaging` instance, given the validated data.
"""
print("Labels:Update")
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
class EMBSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=50)
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `EMBs` instance, given the validated data.
"""
print("Brand:Create")
try:
return EMBs.objects.create(**validated_data)
except IntegrityError:
return EMBs.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `EMBs` instance, given the validated data.
"""
print("Brand:Update")
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
class ManufacturingPlacesSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=50)
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `Brand` instance, given the validated data.
"""
print("Brand:Create")
try:
return ManufacturingPlaces.objects.create(**validated_data)
except IntegrityError:
return ManufacturingPlaces.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
print("Brand:Update")
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
class OriginIngredientsSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=50)
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `Brand` instance, given the validated data.
"""
try:
return OriginIngredients.objects.create(**validated_data)
except IntegrityError:
return OriginIngredients.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
class BrandSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=50)
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `Brand` instance, given the validated data.
"""
print("Brand:Create")
try:
return Brand.objects.create(**validated_data)
except IntegrityError:
return Brand.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
print("Brand:Update")
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
#def validate_name(self, value):
# print('Validate: name')
# return value
#def validate(self, data):
# print('Validate object: name')
# return data
class ArticleSerializer(serializers.ModelSerializer):
brand = BrandSerializer(allow_null = True)
allergenes = AllergenesSerializer(allow_null = True, many=True)
#ingredients = IngredientsSerializer(allow_null = True, many=True)
traces = TracesSerializer(allow_null = True, many=True)
labels = LabelsSerializer(allow_null = True, many=True)
#packagings = PackagingArticleSerializer(allow_null = True, many=True)
embs = EMBSerializer(allow_null = True, many=True)
manufacturingPlaces = ManufacturingPlacesSerializer(allow_null = True, many=True)
originIngredients = OriginIngredientsSerializer(allow_null = True, many=True)
nutritionImage = serializers.ImageField(read_only = True)
ingredientsImage = serializers.ImageField(read_only = True)
frontImage = serializers.ImageField(read_only = True)
category = CategorySerializer(allow_null = True)
class Meta:
model = Article
fields = ('pk','name','brand','EAN','offlink','category',
'quantityPerPackage','unit','novaGroup','nutritionGrade','ecoGrade',
'MwSt','allergenes','traces','embs','labels',
'manufacturingPlaces','originIngredients',
'created','modified',
'nutritionImage','ingredientsImage', 'frontImage') # 'ingredients',
depth = 1
def update(self, instance, validated_data):
print("update method")
print(validated_data)
article = self.fill_deeper_fields(instance, validated_data)
article.name = validated_data.pop('name')
article.EAN = validated_data.pop('EAN')
article.offlink = validated_data.pop('offlink')
article.quantityPerPackage = validated_data.pop('quantityPerPackage')
article.unit = validated_data.pop('unit')
article.novaGroup = validated_data.pop('novaGroup')
article.nutritionGrade = validated_data.pop('nutritionGrade')
article.ecoGrade = validated_data.pop('ecoGrade')
article.MwSt = validated_data.pop('MwSt')
#print("Article before save")
#print(article.labels.get())
article.save()
return article
def create(self, validated_data):
print('create method')
article = Article.objects.create(**validated_data)
article = self.fill_deeper_fields(article, validated_data)
article.name = validated_data.pop('name')
article.EAN = validated_data.pop('EAN')
article.quantityPerPackage = validated_data.pop('quantityPerPackage')
article.unit = validated_data.pop('unit')
article.novaGroup = validated_data.pop('novaGroup')
article.nutritionGrade = validated_data.pop('nutritionGrade')
article.ecoGrade = validated_data.pop('ecoGrade')
article.MwSt = validated_data.pop('MwSt')
return article
def deserialize_multi_field(self, data_list, model):
elements = []
for data in data_list:
if data != None:
#if model == 'ingredients':
# serializer = IngredientsSerializer(
# data=data
# )
if model == 'allergenes':
serializer = AllergenesSerializer(
data=data
)
elif model == 'traces':
serializer = TracesSerializer(
data=data
)
elif model == 'labels':
serializer = LabelsSerializer(
data=data
)
#elif model == 'packaging':
# serializer = PackagingArticleSerializer(
# data=data
# )
elif model == 'embs':
serializer = EMBSerializer(
data=data
)
elif model == 'manufacturingPlaces':
serializer = ManufacturingPlacesSerializer(
data=data
)
elif model == 'originIngredients':
serializer = OriginIngredientsSerializer(
data=data
)
if serializer.is_valid():
ingredients = serializer.save()
elements.append(ingredients)
else:
elements.append(None)
else:
elements.append(None)
return elements
def fill_deeper_fields(self, article, validated_data):
print(validated_data)
brand_data = validated_data.pop('brand')
if brand_data != None:
serializer = BrandSerializer(
data=brand_data
)
if serializer.is_valid():
brand = serializer.save()
article.brand = brand
else:
article.brand = None
else:
article.brand = None
category_data = validated_data.pop('category')
if category_data != None:
serializer = CategorySerializer(
data=category_data
)
if serializer.is_valid():
category = serializer.save()
article.category = category
else:
article.category = None
else:
article.category = None
#data = validated_data.pop('ingredients')
#elements = self.deserialize_multi_field(data, 'ingredients')
#article.ingredients.set(elements)
data = validated_data.pop('allergenes')
elements = self.deserialize_multi_field(data, 'allergenes')
article.allergenes.set(elements)
data = validated_data.pop('traces')
elements = self.deserialize_multi_field(data, 'traces')
article.traces.set(elements)
data = validated_data.pop('labels')
elements = self.deserialize_multi_field(data, 'labels')
article.labels.set(elements)
#data = validated_data.pop('packaging')
#elements = self.deserialize_multi_field(data, 'packaging')
#article.packaging.set(elements)
data = validated_data.pop('embs')
elements = self.deserialize_multi_field(data, 'embs')
article.embs.set(elements)
data = validated_data.pop('manufacturingPlaces')
elements = self.deserialize_multi_field(data, 'manufacturingPlaces')
article.manufacturingPlaces.set(elements)
data = validated_data.pop('originIngredients')
elements = self.deserialize_multi_field(data, 'originIngredients')
article.originIngredients.set(elements)
return article
# Problem: article_id is not readonly, therefore the serializer checks in validation for existence of article in order to create it, we have to prevent this
class PurchaseArticlesSerializer(serializers.ModelSerializer):
id = serializers.SerializerMethodField('pk')
article_id = ArticleSerializer(read_only=True)
#articleSer = ArticlesSerializer(article)
sum_price = serializers.SerializerMethodField()
map = serializers.SerializerMethodField()
class Meta:
model = PurchaseArticle
fields = ('id','quantity','net_weight','price','inSale','article_id','purchase_id','sum_price','map')
#depth = 2
def pk(self, obj):
return obj.pk
def get_sum_price(self, obj):
return int(obj.quantity) * float(obj.price)
def get_map(self, obj):
purchaseImage = Purchase.objects.get(pk=obj.purchase_id).receipeImage
#print(obj.article_id)
articleMap = ArticleMaps.objects.get(article=obj.article_id, receipeImage=purchaseImage)
print(articleMap)
serializedMap = ArticleMapSerzializer(articleMap, many=False)
return serializedMap.data
def create(self, validated_data):
print('create method of PurchaseArticlesSerializer')
print(validated_data)
#article = Article.objects.get(name=validated_data['article_id']['name'],
# brand=validated_data['article_id']['brand'],
# EAN=validated_data['article_id']['EAN'],
# category=validated_data['article_id']['category'])
articleArticle = PurchaseArticle.objects.create(purchase_id=validated_data.pop('purchase_id'),
article_id=validated_data.pop('article_id'),
quantity= validated_data.pop('quantity'),
price = validated_data.pop('price'),
inSale = validated_data.pop('inSale'))
try:
articleArticle.net_weight = validated_data.pop('net_weight')
except KeyError:
pass
return articleArticle
def to_internal_value(self, data):
article_id = data.get('article_id')
internal_data = super().to_internal_value(data)
try:
article = Article.objects.get(pk=article_id['pk'])
except Article.DoesNotExist:
raise ValidationError(
{'article': ['Invalid article primary key']},
code='invalid',
)
internal_data['article_id'] = article
return internal_data
#Serializer for nutrient model
class NutrientSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk')
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=50)
isMacroNutrient = serializers.BooleanField()
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `Nutrient` instance, given the validated data.
"""
#print("Nutrient:Create")
try:
return Nutrient.objects.create(**validated_data)
except IntegrityError:
return Nutrient.objects.get(name=validated_data['name'])
def update(self, instance, validated_data):
"""
Update and return an existing `Nutrient` instance, given the validated data.
"""
#print("Labels:Update")
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
#Serializer for NutrientArticle model
class NutrientArticleSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk',read_only=False)
pk = serializers.IntegerField(read_only=False)
article_id = serializers.IntegerField(source="article.pk")
nutrient = NutrientSerializer()
value = serializers.DecimalField(max_digits=7, decimal_places=2, allow_null = True)
unit = serializers.CharField(max_length=4,allow_null = True)
isEstimated = serializers.BooleanField(allow_null = True)
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `NutrientArticle` instance, given the validated data.
"""
#print("NutrientArticle:Create")
#print(validated_data)
#try:
article = Article.objects.get(pk=validated_data.pop('article')['pk'])
nutrient = Nutrient.objects.get(pk=validated_data.pop('nutrient')['pk'])
return PackagingArticle.objects.create(article=article,
nutrient=nutrient,
value=validated_data.pop('value'),
unit=validated_data.pop('unit'),
isEstimated=validated_data.pop('isEstimated')
)
#except IntegrityError:
# return PackagingArticle.objects.get(article_id=validated_data['article_id'])
def update(self, instance, validated_data):
#print('update method for articlepackaging')
print(validated_data)
print(instance)
print(type(instance))
name = validated_data.pop('nutrient', instance.nutrient)['name']
nutrient = Nutrient.objects.get(name=name)
instance.nutrient = nutrient
instance.value = validated_data.pop('value',instance.value)
instance.unit = validated_data.pop('unit',instance.unit)
instance.isEstimated = validated_data.pop('isEstimated',instance.isEstimated)
instance.save()
return instance
class PackagingArticleSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk',read_only=False)
pk = serializers.IntegerField(read_only=False)
#If we need the whole object:
#article_id = ArticleSerializer()
#otherwise, we just take the article primary key
article_id = serializers.IntegerField(source="article_id.pk")
packaging_id = PackagingSerializer()
weight = serializers.IntegerField()
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `PackagingArticle` instance, given the validated data.
"""
print("PackagingArticle:Create")
print(validated_data)
#try:
article = Article.objects.get(pk=validated_data.pop('article_id')['pk'])
return PackagingArticle.objects.create(article_id=article, weight=validated_data.pop('weight'))
#except IntegrityError:
# return PackagingArticle.objects.get(article_id=validated_data['article_id'])
def update(self, instance, validated_data):
print('update method for articlepackaging')
print(validated_data)
#print(instance.packaging_id)
name = validated_data.pop('packaging_id', instance.packaging_id)['name']
packaging = Packaging.objects.get(name=name)
instance.packaging_id = packaging
instance.weight = validated_data.pop('weight',instance.weight)
instance.save()
return instance
class IngredientsArticleSerializer(serializers.Serializer):
id = serializers.SerializerMethodField('pk',read_only=False)
pk = serializers.IntegerField(read_only=False)
#If we need the whole object:
#article_id = ArticleSerializer()
#otherwise, we just take the article primary key
article = serializers.IntegerField(source="article.pk")
ingredient = IngredientsSerializer()
position = serializers.IntegerField()
percent = serializers.FloatField(allow_null = True)
def pk(self, obj):
return obj.pk
def create(self, validated_data):
"""
Create and return a new `IngredientsArticle` instance, given the validated data.
"""
print("IngredientsArticle:Create")
print(validated_data)
#try:
article = Article.objects.get(pk=validated_data.pop('article')['pk'])
return IngredientsArticle.objects.create(article=article, position=validated_data.pop('position'))
#except IntegrityError:
# return PackagingArticle.objects.get(article_id=validated_data['article_id'])
def update(self, instance, validated_data):
print('update method for articleingredient')
print(validated_data)
#print(instance.packaging_id)
#ingredient_json = validated_data.pop('ingredient', instance.ingredient)
ingredient = Ingredients.objects.get(id=self.initial_data['ingredient']['id'])
instance.ingredient = ingredient
instance.position = validated_data.pop('position',instance.position)
instance.percent = validated_data.pop('percent',instance.percent)
instance.save()
return instance

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Apr 30 13:05:54 2022
@author: elena
"""
#from django.conf import settings
from receipe.consumer import Consumer, ConsumerError
def consume_file(path,
applyBinarize=True,
debug=False,
task_id=None,
scannerFile=False):
print("Start consuming from task")
receipe = Consumer().try_consume_file(
path,
applyBinarize=applyBinarize,
debug=debug,
task_id=task_id,
scannerFile=scannerFile
)
if receipe:
return "Success. New document id {} created".format(
receipe.pk
)
else:
raise ConsumerError("Unknown error: Returned document was null, but "
"no error message was given.")

View File

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

File diff suppressed because it is too large Load Diff