Gruppenaufgaben 5 & 6 überarbeitet

main
Jannes Hikmat 1 year ago
parent 81f2e2a19a
commit 4579880d47

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webprog.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

@ -0,0 +1,23 @@
from django.contrib import admin
from .models import *
@admin.register(Menu)
class MenuAdmin(admin.ModelAdmin):
pass
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
pass
@admin.register(Location)
class LocationAdmin(admin.ModelAdmin):
pass
@admin.register(Day)
class DayAdmin(admin.ModelAdmin):
pass

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

@ -0,0 +1,16 @@
STATUS_VALUES = ['Student', 'Employee', 'Guest']
TYPES = {
'vn': 'Vegan',
've': 'Vegetarisch',
'BIO': 'Bio',
'IN': 'International',
'MV': 'MensaVital',
'S': 'Schwein',
'R': 'Rind',
'G': 'Geflügel',
'L': 'Lamm',
'AGS': 'Artgerechtes Schwein',
'AGR': 'Artgerechtes Rind',
'AGG': 'Artgerechtes Geflügel',
'AGL': 'Artgerechtes Lamm',
}

@ -0,0 +1,11 @@
from django import forms
from .models import *
from datetime import date
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = '__all__'

@ -0,0 +1,142 @@
from django.shortcuts import get_object_or_404
from django.core.exceptions import ObjectDoesNotExist
from django.utils.dateparse import parse_date
from .models import *
from collections import OrderedDict
import datetime
import requests
example_json = [
{"day": "2022-12-19", "menus": [{"art": "Mensa", "name": "Spaghetti Carbonara", "price": "2,35 € / 4,15 € / 4,85 €", "allergens": ["GlW", "Mi"], "types": ["S", "IN"]}, {"art": "Mensa", "name": "Currywurst VEGAN Hot Pot(t) Karamellisierte Balsamico Zwiebeln Kartoffelspalten", "price": "4,05 € / 5,85 € / 6,55 €", "allergens": ["Sf", "Sl", "So", "Sw"], "types": ["ve", "vn"]}, {"art": "Mensa", "name": "Schupfnudeln, Schmorgemüse, Sojasoße", "price": "3,95 € / 5,75 € / 6,45 €", "allergens": ["GlW", "Se", "So"], "types": ["ve", "vn"]}, {"art": "Cafeteria", "name": "Currywurst Pommes Frites", "price": "4,70 € / 4,70 € / 4,70 €", "allergens": ["Sf"], "types": ["AGS"]}, {"art": "Cafeteria", "name": "Hähnchenschnitzel, Knuspermantel", "price": "2,95 € / 2,95 € / 2,95 €", "allergens": ["GlW", "GlG"], "types": ["G"]}, {"art": "Cafeteria", "name": "Kartoffelgratin, Erbsen, Karotten, Gouda", "price": "3,60 € / 3,60 € / 3,60 €", "allergens": ["Ei", "Mi"], "types": ["ve"]}, {"art": "Cafeteria", "name": "Kartoffelgratin, Hähnchenbruststreifen, Gouda", "price": "3,95 € / 3,95 € / 3,95 €", "allergens": ["Ei", "Mi"], "types": ["G"]}, {"art": "Cafeteria", "name": "Ofenkartoffel mit Sour Creme [Bio]", "price": "3,30 € / 3,30 € / 3,30 €", "allergens": ["Mi"], "types": ["ve", "BIO"]}, {"art": "Cafeteria", "name": "OfenkartoffelTomaten- Zwiebel- Marmelade", "price": "3,30 € / 3,30 € / 3,30 €", "allergens": ["Sf", "Sw"], "types": ["ve", "vn"]}, {"art": "Cafeteria", "name": "Ofenkartoffel Hähnchenstreifen", "price": "3,80 € / 3,80 € / 3,80 €", "allergens": ["Mi"], "types": ["G"]}]},
{"day": "2022-12-20", "menus": [{"art": "Mensa", "name": "Linseneintopf Baguettebrot", "price": "2,35 € / 4,15 € / 4,85 €", "allergens": ["Sl", "Sw", "GlW"], "types": ["ve", "vn"]}, {"art": "Mensa", "name": "Phat Krapao Würzige Thai Soja Bolognese, Ingwer, Basilikum, Koriander, Limettenblätter, Mienudeln", "price": "2,80 € / 4,60 € / 5,30 €", "allergens": ["GlW", "GlG", "So"], "types": ["IN", "ve", "vn"]}, {"art": "Mensa", "name": "Bockwurst zum Eintopf", "price": "1,00 € / 1,10 € / 1,20 €", "allergens": [], "types": ["S"]}, {"art": "Mensa", "name": "Filipino Beef Caldereta Basmatireis", "price": "3,50 € / 5,30 € / 6,00 €", "allergens": ["Fi", "GlW", "Mi", "Sf", "So"], "types": ["G", "R", "IN"]}, {"art": "Cafeteria", "name": "Hähnchenschnitzel, Knuspermantel", "price": "2,95 € / 2,95 € / 2,95 €", "allergens": ["GlW", "GlG"], "types": ["G"]}, {"art": "Cafeteria", "name": "Currywurst Pommes Frites", "price": "4,70 € / 4,70 € / 4,70 €", "allergens": ["Sf"], "types": ["AGS"]}, {"art": "Cafeteria", "name": "Kartoffelgratin, Hähnchenbruststreifen, Gouda", "price": "3,95 € / 3,95 € / 3,95 €", "allergens": ["Ei", "Mi"], "types": ["G"]}, {"art": "Cafeteria", "name": "Kartoffelgratin, Erbsen, Karotten, Gouda", "price": "3,60 € / 3,60 € / 3,60 €", "allergens": ["Ei", "Mi"], "types": ["ve"]}, {"art": "Cafeteria", "name": "OfenkartoffelTomaten- Zwiebel- Marmelade", "price": "3,30 € / 3,30 € / 3,30 €", "allergens": ["Sf", "Sw"], "types": ["ve", "vn"]}, {"art": "Cafeteria", "name": "Ofenkartoffel mit Sour Creme [Bio]", "price": "3,30 € / 3,30 € / 3,30 €", "allergens": ["Mi"], "types": ["ve", "BIO"]}, {"art": "Cafeteria", "name": "Ofenkartoffel Hähnchenstreifen", "price": "3,80 € / 3,80 € / 3,80 €", "allergens": ["Mi"], "types": ["G"]}]},
{"day": "2022-12-21", "menus": [{"art": "Mensa", "name": "Currywurst Hot Pot(t) Kartoffelspalten", "price": "3,90 € / 5,70 € / 6,40 €", "allergens": ["Sf", "Sl", "GlW"], "types": ["AGS"]}, {"art": "Mensa", "name": "Pasta, Kürbis- Ricotta- Soße, Walnüsse", "price": "2,35 € / 4,15 € / 4,85 €", "allergens": ["GlW", "Mi", "NW"], "types": ["ve"]}, {"art": "Mensa", "name": "Rindfleischcurry, Kokos, Karotten, Koriander Basmatireis", "price": "4,10 € / 5,90 € / 6,60 €", "allergens": ["GlW", "Mi", "Sf", "So"], "types": ["R"]}, {"art": "Cafeteria", "name": "Kartoffelgratin, Erbsen, Karotten, Gouda", "price": "3,60 € / 3,60 € / 3,60 €", "allergens": ["Ei", "Mi"], "types": ["ve"]}, {"art": "Cafeteria", "name": "Kartoffelgratin, Hähnchenbruststreifen, Gouda", "price": "3,95 € / 3,95 € / 3,95 €", "allergens": ["Ei", "Mi"], "types": ["G"]}, {"art": "Cafeteria", "name": "Ofenkartoffel Hähnchenstreifen", "price": "3,80 € / 3,80 € / 3,80 €", "allergens": ["Mi"], "types": ["G"]}, {"art": "Cafeteria", "name": "Ofenkartoffel mit Sour Creme [Bio]", "price": "3,30 € / 3,30 € / 3,30 €", "allergens": ["Mi"], "types": ["ve", "BIO"]}, {"art": "Cafeteria", "name": "OfenkartoffelTomaten- Zwiebel- Marmelade", "price": "3,30 € / 3,30 € / 3,30 €", "allergens": ["Sf", "Sw"], "types": ["ve", "vn"]}]},
{"day": "2022-12-22", "menus": [{"art": "Mensa", "name": "Spaghetti Carbonara", "price": "2,35 € / 4,15 € / 4,85 €", "allergens": ["Ei", "GlW", "Mi"], "types": ["S", "IN"]}, {"art": "Mensa", "name": "Tortellini, Kicherbsen, Spitzkohl, Cashewkerne und Rosmarin", "price": "3,50 € / 5,30 € / 6,00 €", "allergens": ["GlW", "NC"], "types": ["ve", "vn"]}, {"art": "Mensa", "name": "Bockwurst zum Eintopf", "price": "", "allergens": [], "types": ["S"]}, {"art": "Mensa", "name": "Vegetarisches Schnitzel, mediterran gefüllt Kartoffelpüree Gurkensalat", "price": "3,65 € / 5,45 € / 6,15 €", "allergens": ["Ei", "GlW", "GlH", "Mi"], "types": ["ve"]}, {"art": "Mensa", "name": "Linseneintopf", "price": "2,00 € / 3,80 € / 4,50 €", "allergens": ["Sl", "Sw"], "types": ["ve", "vn"]}, {"art": "Cafeteria", "name": "Kartoffelgratin, Hähnchenbruststreifen, Gouda", "price": "3,95 € / 3,95 € / 3,95 €", "allergens": ["Ei", "Mi"], "types": ["G"]}, {"art": "Cafeteria", "name": "Kartoffelgratin, Erbsen, Karotten, Gouda", "price": "3,60 € / 3,60 € / 3,60 €", "allergens": ["Ei", "Mi"], "types": ["ve"]}, {"art": "Cafeteria", "name": "Ofenkartoffel mit Sour Creme [Bio]", "price": "3,30 € / 3,30 € / 3,30 €", "allergens": ["Mi"], "types": ["ve", "BIO"]}, {"art": "Cafeteria", "name": "Ofenkartoffel Hähnchenstreifen", "price": "3,80 € / 3,80 € / 3,80 €", "allergens": ["Mi"], "types": ["G"]}, {"art": "Cafeteria", "name": "OfenkartoffelTomaten- Zwiebel- Marmelade", "price": "", "allergens": ["Sf", "Sw"], "types": ["ve", "vn"]}]},
{"day": "2022-12-23", "menus": [{"art": "Mensa", "name": "Spaghetti Carbonara", "price": "2,35 € / 4,15 € / 4,85 €", "allergens": ["Ei", "GlW", "Mi"], "types": ["S", "IN"]}, {"art": "Mensa", "name": "Tortellini, Kicherbsen, Spitzkohl, Cashewkerne und Rosmarin", "price": "3,50 € / 5,30 € / 6,00 €", "allergens": ["GlW", "NC"], "types": ["ve", "vn"]}, {"art": "Mensa", "name": "Bockwurst zum Eintopf", "price": "", "allergens": [], "types": ["S"]}, {"art": "Mensa", "name": "Vegetarisches Schnitzel, mediterran gefüllt Kartoffelpüree Gurkensalat", "price": "3,65 € / 5,45 € / 6,15 €", "allergens": ["Ei", "GlW", "GlH", "Mi"], "types": ["ve"]}, {"art": "Mensa", "name": "Linseneintopf", "price": "2,00 € / 3,80 € / 4,50 €", "allergens": ["Sl", "Sw"], "types": ["ve", "vn"]}, {"art": "Cafeteria", "name": "Kartoffelgratin, Hähnchenbruststreifen, Gouda", "price": "3,95 € / 3,95 € / 3,95 €", "allergens": ["Ei", "Mi"], "types": ["G"]}, {"art": "Cafeteria", "name": "Kartoffelgratin, Erbsen, Karotten, Gouda", "price": "3,60 € / 3,60 € / 3,60 €", "allergens": ["Ei", "Mi"], "types": ["ve"]}, {"art": "Cafeteria", "name": "Ofenkartoffel mit Sour Creme [Bio]", "price": "3,30 € / 3,30 € / 3,30 €", "allergens": ["Mi"], "types": ["ve", "BIO"]}, {"art": "Cafeteria", "name": "Ofenkartoffel Hähnchenstreifen", "price": "3,80 € / 3,80 € / 3,80 €", "allergens": ["Mi"], "types": ["G"]}, {"art": "Cafeteria", "name": "OfenkartoffelTomaten- Zwiebel- Marmelade", "price": "", "allergens": ["Sf", "Sw"], "types": ["ve", "vn"]}]}]
def load_data(locations, checked_types, status):
current_week = datetime.date.today().isocalendar().week
days = Day.objects.filter(date__week=current_week)
for location in locations:
menus = Menu.objects.filter(days__in=days, locations=location)
if not menus.exists():
fetched_data = fetch(location.mensa_id)
store(fetched_data, location)
data = formatted_menu_data(locations, checked_types, status)
return data
def formatted_menu_data(locations, checked_types, status):
current_week = datetime.date.today().isocalendar().week
days = Day.objects.filter(date__week=current_week)
formatted_data = {}
for day in days:
formatted_data[str(day.date)] = []
for day in days:
for location in locations:
menus = Menu.objects.filter(days=day, locations=location)
for menu in menus:
# AND or OR filter ???
if set(checked_types).issubset(set(menu.types)): # <-- AND
formatted_data[str(day.date)].append({
'art': menu.art,
'name': menu.name,
'price': menu.price if status is None else menu.get_price[status],
'allergens': menu.get_allergens,
'types': menu.get_types,
'likes': menu.likes,
'location': location.name,
'pk': menu.pk,
})
return formatted_data
def store(data, location):
for day_json in data:
date_string= day_json['day']
menus_json = day_json['menus']
date = parse_date(date_string)
if not Day.objects.filter(date=date).exists():
Day(date=date).save()
day = Day.objects.get(date=date)
for menu_json in menus_json:
art = menu_json['art']
name = menu_json['name']
price = menu_json['price'] or "0,00€ / 0,00€ / 0,00€"
allergens = menu_json['allergens']
types = menu_json['types']
menu_exists = Menu.objects.filter(art=art, name=name, price=price, allergens=allergens, types=types).exists()
if not menu_exists:
Menu(art=art, name=name, price=price, allergens=allergens, types=types).save()
menu = Menu.objects.get(art=art, name=name, price=price, allergens=allergens, types=types)
menu.locations.add(location)
menu.days.add(day)
menu.save()
def fetch(mensa_id: int):
res = requests.get('https://mensa.fly.dev/' + str(mensa_id))
if res.status_code != 200:
raise Exception('request failed')
return res.json()
def models_from_json(mensa_id: int):
for day_json in fetch_menus(mensa_id):
day_date = datetime.datetime.strptime(day_json['day'], '%Y-%m-%d').date()
if not Day.objects.filter(mensa_id=mensa_id, date__contains=day_date):
# create new day model ONLY if it does not yet exist for the given
# day_date(s) and mensa_id.
Day(mensa_id=mensa_id, date=day_date).save()
day = Day.objects.get(mensa_id=mensa_id, date=day_date)
for menu_json in day_json['menus']:
# if meal already exists, simply add day to related menus.
# otherwise, build new meal model from json with the specified menu
# as the first related menu.
if not Menu.objects.filter(name=menu_json['name']):
Menu.from_json(menu_json, day).save()
day.menus.add(Menu.objects.get(name=menu_json['name']))
def days_as_json(mensa_ids: list, types_filter: list, price_filter: str):
cw = datetime.date.today().isocalendar().week
dates = list(dict.fromkeys([day.date for day in Day.objects.filter(date__week=cw)]))
days_list = []
for date in dates:
day_json = { 'day': date, 'menus': [] }
for mensa_id in mensa_ids:
try:
day = Day.objects.get(mensa_id=mensa_id, date=date)
for menu in Menu.objects.filter(day=day, related_types__in=Type.objects.filter(type__in=types_filter) if types_filter else Type.objects.all()).distinct():
day_json['menus'].append({ 'menu': menu, 'mensa_id': mensa_id })
except ObjectDoesNotExist:
pass
days_list.append(day_json)
return days_list

@ -0,0 +1,48 @@
# Generated by Django 4.1.4 on 2022-12-25 15:48
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Menu',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('mensa_id', models.IntegerField()),
('date', models.CharField(max_length=10)),
],
),
migrations.CreateModel(
name='Meal',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=144)),
('type', models.CharField(max_length=144)),
('allergens', models.CharField(max_length=144)),
('tags', models.CharField(max_length=144)),
('likes', models.IntegerField(default=0)),
('dislikes', models.IntegerField(default=0)),
('price_student', models.FloatField(max_length=8)),
('price_employee', models.FloatField(max_length=8)),
('price_guest', models.FloatField(max_length=8)),
('related_menus', models.ManyToManyField(to='mensaviewer.menu')),
],
),
migrations.CreateModel(
name='Comment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('username', models.CharField(default='anon', max_length=144)),
('comment', models.CharField(max_length=720)),
('related_meal', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='mensaviewer.meal')),
],
),
]

@ -0,0 +1,18 @@
# Generated by Django 4.1.4 on 2022-12-26 15:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='menu',
name='date',
field=models.DateField(),
),
]

@ -0,0 +1,18 @@
# Generated by Django 4.1.4 on 2022-12-26 15:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0002_alter_menu_date'),
]
operations = [
migrations.AlterField(
model_name='menu',
name='date',
field=models.DateField(max_length=10),
),
]

@ -0,0 +1,88 @@
# Generated by Django 4.1.4 on 2022-12-26 16:01
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0003_alter_menu_date'),
]
operations = [
migrations.CreateModel(
name='Day',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('mensa_id', models.IntegerField()),
('date', models.DateField(max_length=10)),
],
),
migrations.RemoveField(
model_name='menu',
name='date',
),
migrations.RemoveField(
model_name='menu',
name='mensa_id',
),
migrations.AddField(
model_name='menu',
name='allergens',
field=models.CharField(blank=True, max_length=144, null=True),
),
migrations.AddField(
model_name='menu',
name='dislikes',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='menu',
name='likes',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='menu',
name='name',
field=models.CharField(blank=True, max_length=144, null=True),
),
migrations.AddField(
model_name='menu',
name='price_employee',
field=models.FloatField(default=0, max_length=8),
),
migrations.AddField(
model_name='menu',
name='price_guest',
field=models.FloatField(default=0, max_length=8),
),
migrations.AddField(
model_name='menu',
name='price_student',
field=models.FloatField(default=0, max_length=8),
),
migrations.AddField(
model_name='menu',
name='tags',
field=models.CharField(blank=True, max_length=144, null=True),
),
migrations.AddField(
model_name='menu',
name='type',
field=models.CharField(blank=True, max_length=144, null=True),
),
migrations.AlterField(
model_name='comment',
name='related_meal',
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='mensaviewer.menu'),
),
migrations.DeleteModel(
name='Meal',
),
migrations.AddField(
model_name='menu',
name='related_menus',
field=models.ManyToManyField(to='mensaviewer.day'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 4.1.4 on 2022-12-26 21:04
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0004_day_remove_menu_date_remove_menu_mensa_id_and_more'),
]
operations = [
migrations.RenameField(
model_name='menu',
old_name='related_menus',
new_name='related_days',
),
]

@ -0,0 +1,50 @@
# Generated by Django 4.1.4 on 2022-12-27 00:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0005_rename_related_menus_menu_related_days'),
]
operations = [
migrations.CreateModel(
name='Allergen',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('allergen', models.CharField(blank=True, max_length=16, null=True)),
],
),
migrations.CreateModel(
name='Tag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tag', models.CharField(blank=True, max_length=16, null=True)),
],
),
migrations.RemoveField(
model_name='menu',
name='allergens',
),
migrations.RemoveField(
model_name='menu',
name='tags',
),
migrations.AlterField(
model_name='menu',
name='related_days',
field=models.ManyToManyField(related_name='menus', to='mensaviewer.day'),
),
migrations.AddField(
model_name='menu',
name='related_allergens',
field=models.ManyToManyField(related_name='menus', to='mensaviewer.allergen'),
),
migrations.AddField(
model_name='menu',
name='related_tags',
field=models.ManyToManyField(related_name='menus', to='mensaviewer.tag'),
),
]

@ -0,0 +1,23 @@
# Generated by Django 4.1.4 on 2023-01-02 02:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0006_allergen_tag_remove_menu_allergens_remove_menu_tags_and_more'),
]
operations = [
migrations.AddField(
model_name='allergen',
name='name',
field=models.CharField(blank=True, max_length=144, null=True),
),
migrations.AddField(
model_name='tag',
name='name',
field=models.CharField(blank=True, max_length=144, null=True),
),
]

@ -0,0 +1,27 @@
# Generated by Django 4.1.4 on 2023-01-02 17:22
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0007_allergen_name_tag_name'),
]
operations = [
migrations.RenameModel(
old_name='Tag',
new_name='Type',
),
migrations.RenameField(
model_name='menu',
old_name='related_tags',
new_name='related_types',
),
migrations.RenameField(
model_name='type',
old_name='tag',
new_name='type',
),
]

@ -0,0 +1,18 @@
# Generated by Django 4.1.4 on 2023-01-03 02:20
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0008_rename_tag_type_and_more'),
]
operations = [
migrations.RenameField(
model_name='menu',
old_name='type',
new_name='art',
),
]

@ -0,0 +1,37 @@
# Generated by Django 4.1.4 on 2023-01-03 23:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0009_rename_type_menu_art'),
]
operations = [
migrations.RemoveField(
model_name='menu',
name='related_days',
),
migrations.AddField(
model_name='day',
name='menus',
field=models.ManyToManyField(related_name='day', to='mensaviewer.menu'),
),
migrations.AlterField(
model_name='day',
name='date',
field=models.DateField(max_length=64),
),
migrations.AlterField(
model_name='menu',
name='related_allergens',
field=models.ManyToManyField(related_name='menu', to='mensaviewer.allergen'),
),
migrations.AlterField(
model_name='menu',
name='related_types',
field=models.ManyToManyField(related_name='menu', to='mensaviewer.type'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 4.1.4 on 2023-01-04 03:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0010_remove_menu_related_days_day_menus_alter_day_date_and_more'),
]
operations = [
migrations.RenameField(
model_name='comment',
old_name='related_meal',
new_name='related_menu',
),
]

@ -0,0 +1,84 @@
# Generated by Django 4.1.4 on 2023-01-04 16:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0011_rename_related_meal_comment_related_menu'),
]
operations = [
migrations.CreateModel(
name='Location',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=144, null=True)),
('mensa_id', models.CharField(blank=True, max_length=2, null=True)),
],
),
migrations.RemoveField(
model_name='day',
name='menus',
),
migrations.RemoveField(
model_name='menu',
name='dislikes',
),
migrations.RemoveField(
model_name='menu',
name='price_employee',
),
migrations.RemoveField(
model_name='menu',
name='price_guest',
),
migrations.RemoveField(
model_name='menu',
name='price_student',
),
migrations.RemoveField(
model_name='menu',
name='related_allergens',
),
migrations.RemoveField(
model_name='menu',
name='related_types',
),
migrations.AddField(
model_name='menu',
name='allergens',
field=models.JSONField(blank=True, null=True),
),
migrations.AddField(
model_name='menu',
name='day',
field=models.DateField(blank=True, null=True),
),
migrations.AddField(
model_name='menu',
name='types',
field=models.JSONField(blank=True, null=True),
),
migrations.AlterField(
model_name='comment',
name='related_menu',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mensaviewer.menu'),
),
migrations.DeleteModel(
name='Allergen',
),
migrations.DeleteModel(
name='Day',
),
migrations.DeleteModel(
name='Type',
),
migrations.AddField(
model_name='menu',
name='location',
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='mensaviewer.location'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 4.1.4 on 2023-01-04 22:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0012_location_remove_day_menus_remove_menu_dislikes_and_more'),
]
operations = [
migrations.AddField(
model_name='menu',
name='price',
field=models.CharField(blank=True, max_length=144, null=True),
),
]

@ -0,0 +1,38 @@
# Generated by Django 4.1.4 on 2023-01-04 23:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0013_menu_price'),
]
operations = [
migrations.CreateModel(
name='Day',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(blank=True, null=True)),
],
),
migrations.RemoveField(
model_name='menu',
name='day',
),
migrations.RemoveField(
model_name='menu',
name='location',
),
migrations.AddField(
model_name='menu',
name='locations',
field=models.ManyToManyField(to='mensaviewer.location'),
),
migrations.AddField(
model_name='menu',
name='days',
field=models.ManyToManyField(to='mensaviewer.day'),
),
]

@ -0,0 +1,19 @@
# Generated by Django 4.1.4 on 2023-01-05 00:43
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('mensaviewer', '0014_day_remove_menu_day_remove_menu_location_and_more'),
]
operations = [
migrations.AddField(
model_name='comment',
name='timestamp',
field=models.DateTimeField(blank=True, default=django.utils.timezone.now),
),
]

@ -0,0 +1,60 @@
from django.db import models
from django.utils.timezone import now
from .data import TYPES
class Day(models.Model):
date = models.DateField(null=True, blank=True)
def __str__(self):
return str(self.date)
class Location(models.Model):
name = models.CharField(max_length=144, null=True, blank=True)
mensa_id = models.CharField(max_length=2, null=True, blank=True)
def __str__(self):
return self.name
class Menu(models.Model):
art = models.CharField(max_length=144, null=True, blank=True)
name = models.CharField(max_length=144, null=True, blank=True)
price = models.CharField(max_length=144, null=True, blank=True)
allergens = models.JSONField(null=True, blank=True)
types = models.JSONField(null=True, blank=True)
likes = models.IntegerField(default=0)
locations = models.ManyToManyField(Location)
days = models.ManyToManyField(Day)
def __str__(self):
return self.name
@property
def get_price(self):
return {
'Student': self.price.split(' / ')[0] or 0,
'Employee': self.price.split(' / ')[1] or 0,
'Guest': self.price.split(' / ')[2] or 0,
}
@property
def get_types(self):
return ', '.join([TYPES[type] for type in self.types])
@property
def get_allergens(self):
return ', '.join([allergen for allergen in self.allergens])
@property
def comments(self):
return [comment for comment in Comment.objects.filter(related_menu=self)]
class Comment(models.Model):
related_menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
timestamp = models.DateTimeField(default=now, blank=True)
username = models.CharField(max_length=144, default='anon')
comment = models.CharField(max_length=720)

@ -0,0 +1,23 @@
{% load static %}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="{% static 'base.css' %}">
<title>Mensaviewer | {% block title %}Home{%endblock%}</title>
</head>
<body>
<header>
<h1>Header Title</h1>
</header>
<nav>
<a href="{% url 'mensaviewer:home' %}">Home</a>
<a href="/admin">Admin</a>
</nav>
<main>
{% block content %}
{% endblock %}
</main>
</body>
</html>

@ -0,0 +1,10 @@
{% extends 'base.html' %}
{% block content %}
<form class="" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Comment">
</form>
{% endblock %}

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block content %}
<form class="" method="post">
{% csrf_token %}
{{ form }}
<button type="submit" name="button">Save</button>
</form>
{% endblock %}

@ -0,0 +1,69 @@
{% extends 'base.html' %}
{% block content %}
<form action="{% url 'mensaviewer:home' %}" method="get">
<input type="hidden" name="filterform" value="filter">
<div class="type-filters">
{% for name, id in types %}
<label>
{{ name }}
<input type="checkbox"
name="type"
value={{ id }}
{% if id in checked_types %} checked{% endif %}>
</label>
{% endfor %}
</div>
<div class="status-filter">
{% for status_value in status_values %}
<label>
{{ status_value }}
<input type="radio"
name="status"
value="{{ status_value }}"
{% if status == status_value %} checked{% endif %}>
</label>
{% endfor %}
</div>
<div class="location-filter">
{% for location in locations %}
<label>
{{ location.name }}
<input type="checkbox"
name="location"
value="{{ location.mensa_id }}"
{% if location.mensa_id in selected_locations %} checked{% endif %}>
</label>
{% endfor %}
</div>
<a href="{% url 'mensaviewer:location_create' %}">New Location</a>
<button type="submit">Filter</button>
</form>
<div class="container">
{% for day, menus in menu_data.items %}
<div class="column">
<h2>{{ day }}</h2>
{% for menu in menus %}
<div class="menu {% if menu.mensa_id == 1 %}a-mensa{% elif menu.mensa_id == 6 %}b-mensa{% endif %}">
<a href="{% url 'mensaviewer:menu_detail' menu.pk %}">{{ menu.name }}</a>
<p>{{ menu.art }}</p>
<p>{{ menu.price }}</p>
<p>{{ menu.types }}</p>
<p>{{ menu.allergens }}</p>
<p>{{ menu.likes }}</p>
<a href="{% url 'mensaviewer:like' menu.pk %}?next={{ request.path }}">Like</a>
<a href="{% url 'mensaviewer:dislike' menu.pk %}?next={{ request.path }}">Dislike</a>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endblock %}

@ -0,0 +1,27 @@
{% extends 'base.html' %}
{% block content %}
<div class="menu-detail">
<h1>{{ menu.name }}</h1>
<p>Art: {{ menu.art }}</p>
<p>Price for Students: {{ menu.price_student }}€</p>
<p>Price for Employees: {{ menu.price_employee }}€</p>
<p>Price for Guests: {{ menu.price_guest }}€</p>
<p>Allergens: {{ menu.get_allergens }}</p>
<p>Types: {{ menu.get_types }}</p>
<p>Rating: {{ menu.likes }}</p>
<a href="{% url 'mensaviewer:like' menu.pk %}?next={{ request.path }}">Like</a>
<a href="{% url 'mensaviewer:dislike' menu.pk %}?next={{ request.path }}">Dislike</a>
<a href="{% url 'mensaviewer:comment' menu.pk %}">Comment</a>
<div class="comments">
{% for comment in menu.comments %}
<div class="comment">
<h3>{{ comment.username }}</h3>
<p>{{ comment.comment }}</p>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

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

@ -0,0 +1,12 @@
from django.urls import path
from . import views
app_name = 'mensaviewer'
urlpatterns = [
path('', views.home, name='home'),
path('menu/<int:pk>/', views.MenuDetailView.as_view(), name='menu_detail'),
path('menu/<int:pk>/like/', views.like, name='like'),
path('menu/<int:pk>/dislike/', views.dislike, name='dislike'),
path('menu/<int:pk>/comment/', views.CommentCreateView.as_view(), name='comment'),
path('location/create/', views.LocationCreateView.as_view(), name='location_create')
]

@ -0,0 +1,75 @@
from django.views.generic import DetailView
from django.views.generic.edit import CreateView
from django.shortcuts import render, redirect, reverse
from .models import *
from .helpers import *
from .forms import *
from .data import *
def home(request):
filter_query = request.COOKIES.get('filter_query', '')
if len(request.GET.getlist('filterform')) == 0 and filter_query != '':
return redirect(reverse('mensaviewer:home') + '?' + filter_query)
selected_location_ids = request.GET.getlist('location')
checked_types = request.GET.getlist('type')
status = request.GET.get('status')
selected_locations = Location.objects.filter(mensa_id__in=selected_location_ids)
context = {
'types': [(TYPES[type], type) for type in TYPES],
'checked_types': checked_types,
'status_values': STATUS_VALUES,
'status': status,
'locations': Location.objects.all(),
'selected_locations': selected_location_ids,
'menu_data': load_data(selected_locations, checked_types, status),
}
response = render(request, "flensburg.html", context)
response.set_cookie('filter_query', request.GET.urlencode())
return response
def like(request, pk):
if request.method == 'GET':
menu = Menu.objects.get(pk=pk)
menu.likes += 1
menu.save()
return redirect(reverse('mensaviewer:home') + '?' + request.GET.get('next'))
def dislike(request, pk):
if request.method == 'GET':
menu = Menu.objects.get(pk=pk)
menu.likes -= 1
menu.save()
return redirect(reverse('mensaviewer:home') + '?' + request.GET.get('next'))
class MenuDetailView(DetailView):
model = Menu
template_name = "menu_detail.html"
queryset = Menu.objects.all()
class LocationCreateView(CreateView):
model = Location
fields = '__all__'
template_name = "create_location.html"
success_url = "/"
class CommentCreateView(CreateView):
model = Comment
fields = ['username', 'comment']
template_name = "comment.html"
success_url = '/menu/{pk}/'
def form_valid(self, form):
self.menu = get_object_or_404(Menu, pk=self.kwargs['pk'])
form.instance.menu = self.menu
print(self.menu)
return super().form_valid(form)

@ -0,0 +1,33 @@
* {
font-family: monospace;
box-sizing: border-box;
outline: 1px solid green;
background-color: rgba(16, 16, 16, 0.1)
}
body {
margin: 0;
}
.container {
display: flex;
flex-flow: row wrap;
}
.column {
flex: 1;
flex-flow: column;
}
.menu {
display: flex;
flex-direction: column;
}
.a-mensa {
background-color: rgba(16, 64, 16, 0.2);
}
.b-mensa {
background-color: rgba(16, 16, 64, 0.2);
}

@ -0,0 +1,16 @@
"""
ASGI config for webprog project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webprog.settings')
application = get_asgi_application()

@ -0,0 +1,128 @@
"""
Django settings for webprog project.
Generated by 'django-admin startproject' using Django 4.1.4.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-srl4kvoe3zz=@x^h6aew)s4(&4!$96g%)%ar+yfpp@4t*0oo9('
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['127.0.0.1', '192.168.178.36']
# Application definition
INSTALLED_APPS = [
'mensaviewer.apps.MensaviewerConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'webprog.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'webprog.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR/"static",
]
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

@ -0,0 +1,22 @@
"""webprog URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('mensaviewer.urls'))
]

@ -0,0 +1,16 @@
"""
WSGI config for webprog project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webprog.settings')
application = get_wsgi_application()
Loading…
Cancel
Save