From 81f2e2a19aee777fe3bd4c06a14de8975df24a64 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Mon, 12 Dec 2022 13:43:10 +0000 Subject: [PATCH 1/7] Setting up GitHub Classroom Feedback From 4579880d4724a5357757e8629bc91bdae17dbf4f Mon Sep 17 00:00:00 2001 From: Jannes Hikmat Date: Thu, 5 Jan 2023 03:32:57 +0100 Subject: [PATCH 2/7] =?UTF-8?q?Gruppenaufgaben=205=20&=206=20=C3=BCberarbe?= =?UTF-8?q?itet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manage.py | 22 +++ mensaviewer/__init__.py | 0 mensaviewer/admin.py | 23 +++ mensaviewer/apps.py | 6 + mensaviewer/data.py | 16 ++ mensaviewer/forms.py | 11 ++ mensaviewer/helpers.py | 142 ++++++++++++++++++ mensaviewer/migrations/0001_initial.py | 48 ++++++ .../migrations/0002_alter_menu_date.py | 18 +++ .../migrations/0003_alter_menu_date.py | 18 +++ ...menu_date_remove_menu_mensa_id_and_more.py | 88 +++++++++++ ..._rename_related_menus_menu_related_days.py | 18 +++ ...enu_allergens_remove_menu_tags_and_more.py | 50 ++++++ .../migrations/0007_allergen_name_tag_name.py | 23 +++ .../0008_rename_tag_type_and_more.py | 27 ++++ .../migrations/0009_rename_type_menu_art.py | 18 +++ ..._days_day_menus_alter_day_date_and_more.py | 37 +++++ ...ename_related_meal_comment_related_menu.py | 18 +++ ...day_menus_remove_menu_dislikes_and_more.py | 84 +++++++++++ mensaviewer/migrations/0013_menu_price.py | 18 +++ ..._menu_day_remove_menu_location_and_more.py | 38 +++++ .../migrations/0015_comment_timestamp.py | 19 +++ mensaviewer/migrations/__init__.py | 0 mensaviewer/models.py | 60 ++++++++ mensaviewer/templates/base.html | 23 +++ mensaviewer/templates/comment.html | 10 ++ mensaviewer/templates/create_location.html | 11 ++ mensaviewer/templates/flensburg.html | 69 +++++++++ mensaviewer/templates/menu_detail.html | 27 ++++ mensaviewer/tests.py | 3 + mensaviewer/urls.py | 12 ++ mensaviewer/views.py | 75 +++++++++ static/base.css | 33 ++++ webprog/__init__.py | 0 webprog/asgi.py | 16 ++ webprog/settings.py | 128 ++++++++++++++++ webprog/urls.py | 22 +++ webprog/wsgi.py | 16 ++ 38 files changed, 1247 insertions(+) create mode 100644 manage.py create mode 100644 mensaviewer/__init__.py create mode 100644 mensaviewer/admin.py create mode 100644 mensaviewer/apps.py create mode 100644 mensaviewer/data.py create mode 100644 mensaviewer/forms.py create mode 100644 mensaviewer/helpers.py create mode 100644 mensaviewer/migrations/0001_initial.py create mode 100644 mensaviewer/migrations/0002_alter_menu_date.py create mode 100644 mensaviewer/migrations/0003_alter_menu_date.py create mode 100644 mensaviewer/migrations/0004_day_remove_menu_date_remove_menu_mensa_id_and_more.py create mode 100644 mensaviewer/migrations/0005_rename_related_menus_menu_related_days.py create mode 100644 mensaviewer/migrations/0006_allergen_tag_remove_menu_allergens_remove_menu_tags_and_more.py create mode 100644 mensaviewer/migrations/0007_allergen_name_tag_name.py create mode 100644 mensaviewer/migrations/0008_rename_tag_type_and_more.py create mode 100644 mensaviewer/migrations/0009_rename_type_menu_art.py create mode 100644 mensaviewer/migrations/0010_remove_menu_related_days_day_menus_alter_day_date_and_more.py create mode 100644 mensaviewer/migrations/0011_rename_related_meal_comment_related_menu.py create mode 100644 mensaviewer/migrations/0012_location_remove_day_menus_remove_menu_dislikes_and_more.py create mode 100644 mensaviewer/migrations/0013_menu_price.py create mode 100644 mensaviewer/migrations/0014_day_remove_menu_day_remove_menu_location_and_more.py create mode 100644 mensaviewer/migrations/0015_comment_timestamp.py create mode 100644 mensaviewer/migrations/__init__.py create mode 100644 mensaviewer/models.py create mode 100644 mensaviewer/templates/base.html create mode 100644 mensaviewer/templates/comment.html create mode 100644 mensaviewer/templates/create_location.html create mode 100644 mensaviewer/templates/flensburg.html create mode 100644 mensaviewer/templates/menu_detail.html create mode 100644 mensaviewer/tests.py create mode 100644 mensaviewer/urls.py create mode 100644 mensaviewer/views.py create mode 100644 static/base.css create mode 100644 webprog/__init__.py create mode 100644 webprog/asgi.py create mode 100644 webprog/settings.py create mode 100644 webprog/urls.py create mode 100644 webprog/wsgi.py diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..68dc517 --- /dev/null +++ b/manage.py @@ -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() diff --git a/mensaviewer/__init__.py b/mensaviewer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mensaviewer/admin.py b/mensaviewer/admin.py new file mode 100644 index 0000000..4989254 --- /dev/null +++ b/mensaviewer/admin.py @@ -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 diff --git a/mensaviewer/apps.py b/mensaviewer/apps.py new file mode 100644 index 0000000..6a1cb7e --- /dev/null +++ b/mensaviewer/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MensaviewerConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'mensaviewer' diff --git a/mensaviewer/data.py b/mensaviewer/data.py new file mode 100644 index 0000000..aefcf23 --- /dev/null +++ b/mensaviewer/data.py @@ -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', +} diff --git a/mensaviewer/forms.py b/mensaviewer/forms.py new file mode 100644 index 0000000..cab3579 --- /dev/null +++ b/mensaviewer/forms.py @@ -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__' diff --git a/mensaviewer/helpers.py b/mensaviewer/helpers.py new file mode 100644 index 0000000..f1e0569 --- /dev/null +++ b/mensaviewer/helpers.py @@ -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 diff --git a/mensaviewer/migrations/0001_initial.py b/mensaviewer/migrations/0001_initial.py new file mode 100644 index 0000000..17b6426 --- /dev/null +++ b/mensaviewer/migrations/0001_initial.py @@ -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')), + ], + ), + ] diff --git a/mensaviewer/migrations/0002_alter_menu_date.py b/mensaviewer/migrations/0002_alter_menu_date.py new file mode 100644 index 0000000..c0c3094 --- /dev/null +++ b/mensaviewer/migrations/0002_alter_menu_date.py @@ -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(), + ), + ] diff --git a/mensaviewer/migrations/0003_alter_menu_date.py b/mensaviewer/migrations/0003_alter_menu_date.py new file mode 100644 index 0000000..7ffaf64 --- /dev/null +++ b/mensaviewer/migrations/0003_alter_menu_date.py @@ -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), + ), + ] diff --git a/mensaviewer/migrations/0004_day_remove_menu_date_remove_menu_mensa_id_and_more.py b/mensaviewer/migrations/0004_day_remove_menu_date_remove_menu_mensa_id_and_more.py new file mode 100644 index 0000000..1073589 --- /dev/null +++ b/mensaviewer/migrations/0004_day_remove_menu_date_remove_menu_mensa_id_and_more.py @@ -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'), + ), + ] diff --git a/mensaviewer/migrations/0005_rename_related_menus_menu_related_days.py b/mensaviewer/migrations/0005_rename_related_menus_menu_related_days.py new file mode 100644 index 0000000..2b351bd --- /dev/null +++ b/mensaviewer/migrations/0005_rename_related_menus_menu_related_days.py @@ -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', + ), + ] diff --git a/mensaviewer/migrations/0006_allergen_tag_remove_menu_allergens_remove_menu_tags_and_more.py b/mensaviewer/migrations/0006_allergen_tag_remove_menu_allergens_remove_menu_tags_and_more.py new file mode 100644 index 0000000..41d2c6c --- /dev/null +++ b/mensaviewer/migrations/0006_allergen_tag_remove_menu_allergens_remove_menu_tags_and_more.py @@ -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'), + ), + ] diff --git a/mensaviewer/migrations/0007_allergen_name_tag_name.py b/mensaviewer/migrations/0007_allergen_name_tag_name.py new file mode 100644 index 0000000..08f6493 --- /dev/null +++ b/mensaviewer/migrations/0007_allergen_name_tag_name.py @@ -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), + ), + ] diff --git a/mensaviewer/migrations/0008_rename_tag_type_and_more.py b/mensaviewer/migrations/0008_rename_tag_type_and_more.py new file mode 100644 index 0000000..d5fa164 --- /dev/null +++ b/mensaviewer/migrations/0008_rename_tag_type_and_more.py @@ -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', + ), + ] diff --git a/mensaviewer/migrations/0009_rename_type_menu_art.py b/mensaviewer/migrations/0009_rename_type_menu_art.py new file mode 100644 index 0000000..f232b93 --- /dev/null +++ b/mensaviewer/migrations/0009_rename_type_menu_art.py @@ -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', + ), + ] diff --git a/mensaviewer/migrations/0010_remove_menu_related_days_day_menus_alter_day_date_and_more.py b/mensaviewer/migrations/0010_remove_menu_related_days_day_menus_alter_day_date_and_more.py new file mode 100644 index 0000000..a6285f9 --- /dev/null +++ b/mensaviewer/migrations/0010_remove_menu_related_days_day_menus_alter_day_date_and_more.py @@ -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'), + ), + ] diff --git a/mensaviewer/migrations/0011_rename_related_meal_comment_related_menu.py b/mensaviewer/migrations/0011_rename_related_meal_comment_related_menu.py new file mode 100644 index 0000000..f8a6f36 --- /dev/null +++ b/mensaviewer/migrations/0011_rename_related_meal_comment_related_menu.py @@ -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', + ), + ] diff --git a/mensaviewer/migrations/0012_location_remove_day_menus_remove_menu_dislikes_and_more.py b/mensaviewer/migrations/0012_location_remove_day_menus_remove_menu_dislikes_and_more.py new file mode 100644 index 0000000..02d8036 --- /dev/null +++ b/mensaviewer/migrations/0012_location_remove_day_menus_remove_menu_dislikes_and_more.py @@ -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'), + ), + ] diff --git a/mensaviewer/migrations/0013_menu_price.py b/mensaviewer/migrations/0013_menu_price.py new file mode 100644 index 0000000..7de9fa5 --- /dev/null +++ b/mensaviewer/migrations/0013_menu_price.py @@ -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), + ), + ] diff --git a/mensaviewer/migrations/0014_day_remove_menu_day_remove_menu_location_and_more.py b/mensaviewer/migrations/0014_day_remove_menu_day_remove_menu_location_and_more.py new file mode 100644 index 0000000..5879d1a --- /dev/null +++ b/mensaviewer/migrations/0014_day_remove_menu_day_remove_menu_location_and_more.py @@ -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'), + ), + ] diff --git a/mensaviewer/migrations/0015_comment_timestamp.py b/mensaviewer/migrations/0015_comment_timestamp.py new file mode 100644 index 0000000..d670106 --- /dev/null +++ b/mensaviewer/migrations/0015_comment_timestamp.py @@ -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), + ), + ] diff --git a/mensaviewer/migrations/__init__.py b/mensaviewer/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mensaviewer/models.py b/mensaviewer/models.py new file mode 100644 index 0000000..a5679c1 --- /dev/null +++ b/mensaviewer/models.py @@ -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) diff --git a/mensaviewer/templates/base.html b/mensaviewer/templates/base.html new file mode 100644 index 0000000..e8f51df --- /dev/null +++ b/mensaviewer/templates/base.html @@ -0,0 +1,23 @@ +{% load static %} + + + + + + + Mensaviewer | {% block title %}Home{%endblock%} + + +
+

Header Title

+
+ +
+ {% block content %} + {% endblock %} +
+ + diff --git a/mensaviewer/templates/comment.html b/mensaviewer/templates/comment.html new file mode 100644 index 0000000..ac0e7bd --- /dev/null +++ b/mensaviewer/templates/comment.html @@ -0,0 +1,10 @@ +{% extends 'base.html' %} + +{% block content %} + +
+ {% csrf_token %} + {{ form }} + +
+{% endblock %} diff --git a/mensaviewer/templates/create_location.html b/mensaviewer/templates/create_location.html new file mode 100644 index 0000000..481904b --- /dev/null +++ b/mensaviewer/templates/create_location.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} + +{% block content %} + +
+ {% csrf_token %} + {{ form }} + +
+ +{% endblock %} diff --git a/mensaviewer/templates/flensburg.html b/mensaviewer/templates/flensburg.html new file mode 100644 index 0000000..1da86c9 --- /dev/null +++ b/mensaviewer/templates/flensburg.html @@ -0,0 +1,69 @@ +{% extends 'base.html' %} + +{% block content %} + +
+ +
+ {% for name, id in types %} + + {% endfor %} +
+
+ {% for status_value in status_values %} + + {% endfor %} +
+
+ {% for location in locations %} + + {% endfor %} +
+ New Location + +
+ +
+ {% for day, menus in menu_data.items %} +
+

{{ day }}

+ + {% for menu in menus %} + + {% endfor %} + + + +
+ {% endfor %} +
+ + +{% endblock %} diff --git a/mensaviewer/templates/menu_detail.html b/mensaviewer/templates/menu_detail.html new file mode 100644 index 0000000..de9acdc --- /dev/null +++ b/mensaviewer/templates/menu_detail.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block content %} + + + +{% endblock %} diff --git a/mensaviewer/tests.py b/mensaviewer/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/mensaviewer/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/mensaviewer/urls.py b/mensaviewer/urls.py new file mode 100644 index 0000000..825038f --- /dev/null +++ b/mensaviewer/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from . import views + +app_name = 'mensaviewer' +urlpatterns = [ + path('', views.home, name='home'), + path('menu//', views.MenuDetailView.as_view(), name='menu_detail'), + path('menu//like/', views.like, name='like'), + path('menu//dislike/', views.dislike, name='dislike'), + path('menu//comment/', views.CommentCreateView.as_view(), name='comment'), + path('location/create/', views.LocationCreateView.as_view(), name='location_create') +] diff --git a/mensaviewer/views.py b/mensaviewer/views.py new file mode 100644 index 0000000..d11481e --- /dev/null +++ b/mensaviewer/views.py @@ -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) diff --git a/static/base.css b/static/base.css new file mode 100644 index 0000000..fb859d9 --- /dev/null +++ b/static/base.css @@ -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); +} diff --git a/webprog/__init__.py b/webprog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/webprog/asgi.py b/webprog/asgi.py new file mode 100644 index 0000000..efea017 --- /dev/null +++ b/webprog/asgi.py @@ -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() diff --git a/webprog/settings.py b/webprog/settings.py new file mode 100644 index 0000000..a03a411 --- /dev/null +++ b/webprog/settings.py @@ -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' diff --git a/webprog/urls.py b/webprog/urls.py new file mode 100644 index 0000000..c5c9b59 --- /dev/null +++ b/webprog/urls.py @@ -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')) +] diff --git a/webprog/wsgi.py b/webprog/wsgi.py new file mode 100644 index 0000000..8d2b237 --- /dev/null +++ b/webprog/wsgi.py @@ -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() From 7893cbcb6f8f3874e7dd07ea346951b506af2be7 Mon Sep 17 00:00:00 2001 From: zb0xa1 Date: Sun, 8 Jan 2023 14:17:42 +0100 Subject: [PATCH 3/7] Added data, forms, and models. --- mensaviewer/data.py | 30 +++++++++++++++++++ mensaviewer/forms.py | 12 ++++++-- mensaviewer/models.py | 70 +++++++++++++++++++++++++++---------------- 3 files changed, 83 insertions(+), 29 deletions(-) diff --git a/mensaviewer/data.py b/mensaviewer/data.py index aefcf23..551b8d5 100644 --- a/mensaviewer/data.py +++ b/mensaviewer/data.py @@ -1,4 +1,31 @@ STATUS_VALUES = ['Student', 'Employee', 'Guest'] +ALLERGENS = { + 'Ei': 'Ei', + 'En': 'Erdnüsse', + 'Fi': 'Fisch', + 'Gl': 'Glutenhaltiges Getreide', + 'GlD': 'Dinkel', + 'GlG': 'Gerste', + 'GlH': 'Hafer', + 'GlR': 'Roggen', + 'GlW': 'Weizen', + 'Kr': 'Krebstiere', + 'Lac': 'Laktose', + 'Lu': 'Lupinen', + 'Mi': 'Milch', + 'Sc': 'Schalenfrüchte', + 'ScC': 'Cashews', + 'ScH': 'Haselnüsse', + 'ScM': 'Mandeln', + 'ScP': 'Pistazien', + 'ScW': 'Walnüsse', + 'Se': 'Sesam', + 'Sf': 'Senf', + 'Sl': 'Sellerie', + 'So': 'Soja', + 'Sw': 'Schwefeldioxid / Sulphite', + 'Wt': 'Weichtiere', +} TYPES = { 'vn': 'Vegan', 've': 'Vegetarisch', @@ -14,3 +41,6 @@ TYPES = { 'AGG': 'Artgerechtes Geflügel', 'AGL': 'Artgerechtes Lamm', } +ALLERGENS_CHOICES = ((key, ALLERGENS[key]) for key in ALLERGENS) +TYPES_CHOICES = ((key, TYPES[key]) for key in TYPES) +ART_CHOICES = (('Mensa', 'Mensa'), ('Cafeteria', 'Cafeteria')) diff --git a/mensaviewer/forms.py b/mensaviewer/forms.py index cab3579..ffdaedd 100644 --- a/mensaviewer/forms.py +++ b/mensaviewer/forms.py @@ -1,11 +1,17 @@ from django import forms from .models import * +from .data import TYPES_CHOICES, ALLERGENS_CHOICES from datetime import date -class CommentForm(forms.ModelForm): +class MenuForm(forms.ModelForm): class Meta: - model = Comment - fields = '__all__' + model = Menu + fields = ['art', 'name', 'price', 'allergens', 'types', 'day'] + widgets = { + 'types': forms.CheckboxSelectMultiple(choices=TYPES_CHOICES), + 'allergens': forms.CheckboxSelectMultiple(choices=ALLERGENS_CHOICES), + 'day': forms.DateInput(attrs={'type': 'date'}, format="%d.%m.%Y"), + } diff --git a/mensaviewer/models.py b/mensaviewer/models.py index a5679c1..ffe945b 100644 --- a/mensaviewer/models.py +++ b/mensaviewer/models.py @@ -1,44 +1,54 @@ 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) +from .data import TYPES, ART_CHOICES class Location(models.Model): + city = models.CharField(max_length=144, null=True, blank=True) 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 + return f"{self.city} - {self.name} [{self.mensa_id}]" + + @property + def menus(self): + return [menu for menu in Menu.objects.filter(location=self)] + + @property + def news_articles(self): + return [news_article for news_article in NewsArticle.objects.filter(location=self)] 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) + art = models.CharField(max_length=24, choices=ART_CHOICES, default='M') + name = models.CharField(max_length=144, default='---', blank=True) + price = models.CharField(max_length=144, default='---', blank=True) + allergens = models.JSONField(default=list, blank=True) + types = models.JSONField(default=list, blank=True) + day = models.DateField(null=True, blank=True) likes = models.IntegerField(default=0) - locations = models.ManyToManyField(Location) - days = models.ManyToManyField(Day) + location = models.ForeignKey(Location, on_delete=models.CASCADE, null=True, + blank=True) def __str__(self): - return self.name + return f"{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, - } + try: + return { + 'Student': self.price.split(' / ')[0], + 'Employee': self.price.split(' / ')[1], + 'Guest': self.price.split(' / ')[2], + } + except IndexError: + return { + 'Student': "0,00 €", + 'Employee': "0,00 €", + 'Guest': "0,00 €", + } @property def get_types(self): @@ -50,11 +60,19 @@ class Menu(models.Model): @property def comments(self): - return [comment for comment in Comment.objects.filter(related_menu=self)] + return [comment for comment in Comment.objects.filter(menu=self)] class Comment(models.Model): - related_menu = models.ForeignKey(Menu, on_delete=models.CASCADE) + 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) + author = models.CharField(max_length=144, default='anon') + text = models.TextField(null=True, blank=True) + + +class NewsArticle(models.Model): + location = models.ForeignKey(Location, on_delete=models.CASCADE) + timestamp = models.DateTimeField(default=now, blank=True) + author = models.CharField(max_length=144, default='anon') + title = models.CharField(max_length=144, blank=True) + text = models.TextField(null=True, blank=True) From 33870561208c545933c03a877c9d29d168ad3fa8 Mon Sep 17 00:00:00 2001 From: Jannes Hikmat Date: Sun, 8 Jan 2023 19:51:05 +0100 Subject: [PATCH 4/7] update --- mensaviewer/admin.py | 4 +- mensaviewer/forms.py | 6 +- mensaviewer/helpers.py | 96 ++------- ...6_remove_menu_days_delete_day_menu_days.py | 25 +++ ...e_menu_locations_location_city_and_more.py | 37 ++++ ...rename_username_comment_author_and_more.py | 42 ++++ ...ter_comment_text_alter_newsarticle_text.py | 23 +++ .../migrations/0020_newsarticle_timestamp.py | 19 ++ .../migrations/0021_newsarticle_title.py | 18 ++ ...alter_menu_art_alter_menu_name_and_more.py | 38 ++++ ...alter_menu_art_alter_menu_name_and_more.py | 38 ++++ mensaviewer/models.py | 10 +- mensaviewer/templates/base.html | 25 ++- mensaviewer/templates/comment/delete.html | 12 ++ mensaviewer/templates/comment/detail.html | 12 ++ mensaviewer/templates/comment/edit.html | 11 + mensaviewer/templates/comment/list.html | 31 +++ mensaviewer/templates/custom.html | 60 ++++++ mensaviewer/templates/flensburg.html | 42 ++-- mensaviewer/templates/location/delete.html | 12 ++ mensaviewer/templates/location/detail.html | 29 +++ mensaviewer/templates/location/edit.html | 11 + mensaviewer/templates/location/list.html | 31 +++ mensaviewer/templates/menu/delete.html | 12 ++ mensaviewer/templates/menu/detail.html | 28 +++ mensaviewer/templates/menu/edit.html | 11 + mensaviewer/templates/menu/list.html | 31 +++ mensaviewer/templates/news/delete.html | 12 ++ mensaviewer/templates/news/detail.html | 12 ++ mensaviewer/templates/news/edit.html | 11 + mensaviewer/templates/news/list.html | 33 +++ mensaviewer/urls.py | 42 +++- mensaviewer/views.py | 191 +++++++++++++++--- static/base.css | 182 ++++++++++++++++- 34 files changed, 1046 insertions(+), 151 deletions(-) create mode 100644 mensaviewer/migrations/0016_remove_menu_days_delete_day_menu_days.py create mode 100644 mensaviewer/migrations/0017_remove_menu_days_remove_menu_locations_location_city_and_more.py create mode 100644 mensaviewer/migrations/0018_rename_username_comment_author_and_more.py create mode 100644 mensaviewer/migrations/0019_alter_comment_text_alter_newsarticle_text.py create mode 100644 mensaviewer/migrations/0020_newsarticle_timestamp.py create mode 100644 mensaviewer/migrations/0021_newsarticle_title.py create mode 100644 mensaviewer/migrations/0022_alter_menu_allergens_alter_menu_art_alter_menu_name_and_more.py create mode 100644 mensaviewer/migrations/0023_alter_menu_allergens_alter_menu_art_alter_menu_name_and_more.py create mode 100644 mensaviewer/templates/comment/delete.html create mode 100644 mensaviewer/templates/comment/detail.html create mode 100644 mensaviewer/templates/comment/edit.html create mode 100644 mensaviewer/templates/comment/list.html create mode 100644 mensaviewer/templates/custom.html create mode 100644 mensaviewer/templates/location/delete.html create mode 100644 mensaviewer/templates/location/detail.html create mode 100644 mensaviewer/templates/location/edit.html create mode 100644 mensaviewer/templates/location/list.html create mode 100644 mensaviewer/templates/menu/delete.html create mode 100644 mensaviewer/templates/menu/detail.html create mode 100644 mensaviewer/templates/menu/edit.html create mode 100644 mensaviewer/templates/menu/list.html create mode 100644 mensaviewer/templates/news/delete.html create mode 100644 mensaviewer/templates/news/detail.html create mode 100644 mensaviewer/templates/news/edit.html create mode 100644 mensaviewer/templates/news/list.html diff --git a/mensaviewer/admin.py b/mensaviewer/admin.py index 4989254..38a0f6f 100644 --- a/mensaviewer/admin.py +++ b/mensaviewer/admin.py @@ -18,6 +18,6 @@ class LocationAdmin(admin.ModelAdmin): pass -@admin.register(Day) -class DayAdmin(admin.ModelAdmin): +@admin.register(NewsArticle) +class NewsArticleAdmin(admin.ModelAdmin): pass diff --git a/mensaviewer/forms.py b/mensaviewer/forms.py index ffdaedd..243ac0a 100644 --- a/mensaviewer/forms.py +++ b/mensaviewer/forms.py @@ -9,9 +9,7 @@ from datetime import date class MenuForm(forms.ModelForm): class Meta: model = Menu - fields = ['art', 'name', 'price', 'allergens', 'types', 'day'] + fields = ['location', 'art', 'name', 'price', 'allergens', 'types', 'day'] widgets = { - 'types': forms.CheckboxSelectMultiple(choices=TYPES_CHOICES), - 'allergens': forms.CheckboxSelectMultiple(choices=ALLERGENS_CHOICES), - 'day': forms.DateInput(attrs={'type': 'date'}, format="%d.%m.%Y"), + 'day': forms.DateInput(attrs={'type': 'date'}), } diff --git a/mensaviewer/helpers.py b/mensaviewer/helpers.py index f1e0569..4208655 100644 --- a/mensaviewer/helpers.py +++ b/mensaviewer/helpers.py @@ -20,46 +20,39 @@ example_json = [ 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) + menus = Menu.objects.filter(day__week=current_week, location=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) + today = datetime.date.today() + days = [today + datetime.timedelta(days=i) + for i in range(0 - today.weekday(), 5 - today.weekday())] formatted_data = {} - - for day in days: - formatted_data[str(day.date)] = [] - for day in days: + if day not in formatted_data: + formatted_data[day] = [] for location in locations: - menus = Menu.objects.filter(days=day, locations=location) + menus = Menu.objects.filter(location=location, day=day) for menu in menus: - - # AND or OR filter ??? if set(checked_types).issubset(set(menu.types)): # <-- AND - - - - formatted_data[str(day.date)].append({ + formatted_data[day].append({ 'art': menu.art, 'name': menu.name, - 'price': menu.price if status is None else menu.get_price[status], + '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, + 'location': location, 'pk': menu.pk, }) return formatted_data @@ -67,32 +60,21 @@ def formatted_menu_data(locations, checked_types, status): def store(data, location): for day_json in data: - date_string= day_json['day'] + 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) - + day = parse_date(date_string) 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€" + 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() - + menu_exists = Menu.objects.filter(art=art, name=name, price=price, + allergens=allergens, types=types, + day=day).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() + Menu(art=art, name=name, price=price, allergens=allergens, + types=types, day=day, location=location).save() def fetch(mensa_id: int): @@ -100,43 +82,3 @@ def fetch(mensa_id: int): 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 diff --git a/mensaviewer/migrations/0016_remove_menu_days_delete_day_menu_days.py b/mensaviewer/migrations/0016_remove_menu_days_delete_day_menu_days.py new file mode 100644 index 0000000..ba441b8 --- /dev/null +++ b/mensaviewer/migrations/0016_remove_menu_days_delete_day_menu_days.py @@ -0,0 +1,25 @@ +# Generated by Django 4.1.4 on 2023-01-06 00:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mensaviewer', '0015_comment_timestamp'), + ] + + operations = [ + migrations.RemoveField( + model_name='menu', + name='days', + ), + migrations.DeleteModel( + name='Day', + ), + migrations.AddField( + model_name='menu', + name='days', + field=models.JSONField(blank=True, null=True), + ), + ] diff --git a/mensaviewer/migrations/0017_remove_menu_days_remove_menu_locations_location_city_and_more.py b/mensaviewer/migrations/0017_remove_menu_days_remove_menu_locations_location_city_and_more.py new file mode 100644 index 0000000..74db3bd --- /dev/null +++ b/mensaviewer/migrations/0017_remove_menu_days_remove_menu_locations_location_city_and_more.py @@ -0,0 +1,37 @@ +# Generated by Django 4.1.4 on 2023-01-06 22:11 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mensaviewer', '0016_remove_menu_days_delete_day_menu_days'), + ] + + operations = [ + migrations.RemoveField( + model_name='menu', + name='days', + ), + migrations.RemoveField( + model_name='menu', + name='locations', + ), + migrations.AddField( + model_name='location', + name='city', + field=models.CharField(blank=True, max_length=144, null=True), + ), + migrations.AddField( + model_name='menu', + name='day', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='menu', + name='location', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mensaviewer.location'), + ), + ] diff --git a/mensaviewer/migrations/0018_rename_username_comment_author_and_more.py b/mensaviewer/migrations/0018_rename_username_comment_author_and_more.py new file mode 100644 index 0000000..e8bc311 --- /dev/null +++ b/mensaviewer/migrations/0018_rename_username_comment_author_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 4.1.4 on 2023-01-06 22:32 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mensaviewer', '0017_remove_menu_days_remove_menu_locations_location_city_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='comment', + old_name='username', + new_name='author', + ), + migrations.RenameField( + model_name='comment', + old_name='related_menu', + new_name='menu', + ), + migrations.RemoveField( + model_name='comment', + name='comment', + ), + migrations.AddField( + model_name='comment', + name='text', + field=models.CharField(blank=True, max_length=512, null=True), + ), + migrations.CreateModel( + name='NewsArticle', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('author', models.CharField(default='anon', max_length=144)), + ('text', models.CharField(blank=True, max_length=8192, null=True)), + ('location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mensaviewer.location')), + ], + ), + ] diff --git a/mensaviewer/migrations/0019_alter_comment_text_alter_newsarticle_text.py b/mensaviewer/migrations/0019_alter_comment_text_alter_newsarticle_text.py new file mode 100644 index 0000000..18f1dc5 --- /dev/null +++ b/mensaviewer/migrations/0019_alter_comment_text_alter_newsarticle_text.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.4 on 2023-01-06 22:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mensaviewer', '0018_rename_username_comment_author_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='comment', + name='text', + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name='newsarticle', + name='text', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/mensaviewer/migrations/0020_newsarticle_timestamp.py b/mensaviewer/migrations/0020_newsarticle_timestamp.py new file mode 100644 index 0000000..22c1ef3 --- /dev/null +++ b/mensaviewer/migrations/0020_newsarticle_timestamp.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.4 on 2023-01-08 01:01 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('mensaviewer', '0019_alter_comment_text_alter_newsarticle_text'), + ] + + operations = [ + migrations.AddField( + model_name='newsarticle', + name='timestamp', + field=models.DateTimeField(blank=True, default=django.utils.timezone.now), + ), + ] diff --git a/mensaviewer/migrations/0021_newsarticle_title.py b/mensaviewer/migrations/0021_newsarticle_title.py new file mode 100644 index 0000000..68be4bf --- /dev/null +++ b/mensaviewer/migrations/0021_newsarticle_title.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.4 on 2023-01-08 02:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mensaviewer', '0020_newsarticle_timestamp'), + ] + + operations = [ + migrations.AddField( + model_name='newsarticle', + name='title', + field=models.CharField(blank=True, max_length=144), + ), + ] diff --git a/mensaviewer/migrations/0022_alter_menu_allergens_alter_menu_art_alter_menu_name_and_more.py b/mensaviewer/migrations/0022_alter_menu_allergens_alter_menu_art_alter_menu_name_and_more.py new file mode 100644 index 0000000..e2dac3f --- /dev/null +++ b/mensaviewer/migrations/0022_alter_menu_allergens_alter_menu_art_alter_menu_name_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.1.4 on 2023-01-08 03:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mensaviewer', '0021_newsarticle_title'), + ] + + operations = [ + migrations.AlterField( + model_name='menu', + name='allergens', + field=models.JSONField(blank=True, default=[]), + ), + migrations.AlterField( + model_name='menu', + name='art', + field=models.CharField(choices=[('M', 'Mensa'), ('C', 'Cafeteria')], default='M', max_length=1), + ), + migrations.AlterField( + model_name='menu', + name='name', + field=models.CharField(blank=True, default='', max_length=144), + ), + migrations.AlterField( + model_name='menu', + name='price', + field=models.CharField(blank=True, default='', max_length=144), + ), + migrations.AlterField( + model_name='menu', + name='types', + field=models.JSONField(blank=True, default=[]), + ), + ] diff --git a/mensaviewer/migrations/0023_alter_menu_allergens_alter_menu_art_alter_menu_name_and_more.py b/mensaviewer/migrations/0023_alter_menu_allergens_alter_menu_art_alter_menu_name_and_more.py new file mode 100644 index 0000000..83050e1 --- /dev/null +++ b/mensaviewer/migrations/0023_alter_menu_allergens_alter_menu_art_alter_menu_name_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.1.4 on 2023-01-08 18:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mensaviewer', '0022_alter_menu_allergens_alter_menu_art_alter_menu_name_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='menu', + name='allergens', + field=models.JSONField(blank=True, default=list), + ), + migrations.AlterField( + model_name='menu', + name='art', + field=models.CharField(choices=[('Mensa', 'Mensa'), ('Cafeteria', 'Cafeteria')], default='M', max_length=24), + ), + migrations.AlterField( + model_name='menu', + name='name', + field=models.CharField(blank=True, default='---', max_length=144), + ), + migrations.AlterField( + model_name='menu', + name='price', + field=models.CharField(blank=True, default='---', max_length=144), + ), + migrations.AlterField( + model_name='menu', + name='types', + field=models.JSONField(blank=True, default=list), + ), + ] diff --git a/mensaviewer/models.py b/mensaviewer/models.py index ffe945b..d820e14 100644 --- a/mensaviewer/models.py +++ b/mensaviewer/models.py @@ -10,7 +10,7 @@ class Location(models.Model): mensa_id = models.CharField(max_length=2, null=True, blank=True) def __str__(self): - return f"{self.city} - {self.name} [{self.mensa_id}]" + return f"{self.city} - {self.name}" @property def menus(self): @@ -33,7 +33,7 @@ class Menu(models.Model): blank=True) def __str__(self): - return f"{self.name}" + return f"{self.name} - {self.location} | {self.day}" @property def get_price(self): @@ -69,6 +69,9 @@ class Comment(models.Model): author = models.CharField(max_length=144, default='anon') text = models.TextField(null=True, blank=True) + def __str__(self): + return f"{self.menu} - {self.author} | {self.timestamp}" + class NewsArticle(models.Model): location = models.ForeignKey(Location, on_delete=models.CASCADE) @@ -76,3 +79,6 @@ class NewsArticle(models.Model): author = models.CharField(max_length=144, default='anon') title = models.CharField(max_length=144, blank=True) text = models.TextField(null=True, blank=True) + + def __str__(self): + return f"{self.title} | {self.title} - {self.author} | {self.timestamp}" diff --git a/mensaviewer/templates/base.html b/mensaviewer/templates/base.html index e8f51df..2c8f291 100644 --- a/mensaviewer/templates/base.html +++ b/mensaviewer/templates/base.html @@ -5,19 +5,28 @@ - Mensaviewer | {% block title %}Home{%endblock%} + Mensaviewer | {% block title %}Home{% endblock %}
-

Header Title

+ {% block header %}

Header Title

{% endblock %}
-
- {% block content %} - {% endblock %} + +
+ {% block content %} + {% endblock %} +
diff --git a/mensaviewer/templates/comment/delete.html b/mensaviewer/templates/comment/delete.html new file mode 100644 index 0000000..ac3307c --- /dev/null +++ b/mensaviewer/templates/comment/delete.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} + +
+ {% csrf_token %} +

Are you sure you want to delete "{{ object }}"?

+ {{ form }} + +
+ +{% endblock %} diff --git a/mensaviewer/templates/comment/detail.html b/mensaviewer/templates/comment/detail.html new file mode 100644 index 0000000..103231e --- /dev/null +++ b/mensaviewer/templates/comment/detail.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} + +
+

By {{ comment.author }}

+

Timestamp: {{ comment.timestamp }}

+

Menu: {{ comment.menu.name }}

+

Text: {{ comment.text }}

+
+ +{% endblock %} diff --git a/mensaviewer/templates/comment/edit.html b/mensaviewer/templates/comment/edit.html new file mode 100644 index 0000000..f56eaaf --- /dev/null +++ b/mensaviewer/templates/comment/edit.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} + +{% block content %} + +
+ {% csrf_token %} + {{ form }} + +
+ +{% endblock %} diff --git a/mensaviewer/templates/comment/list.html b/mensaviewer/templates/comment/list.html new file mode 100644 index 0000000..d1857bd --- /dev/null +++ b/mensaviewer/templates/comment/list.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} + +{% block content %} + +

Comments

+ + + + + + + + + + + + + {% for comment in object_list %} + + + + + + + + {% endfor %} +
+ Add Comment +
AuthorTimestampMenu
{{ comment.author }}{{ comment.timestamp }}{{ comment.menu }}EditDelete
+ +{% endblock %} diff --git a/mensaviewer/templates/custom.html b/mensaviewer/templates/custom.html new file mode 100644 index 0000000..ce1808e --- /dev/null +++ b/mensaviewer/templates/custom.html @@ -0,0 +1,60 @@ +{% extends 'base.html' %} + +{% block title %}Custom Locations{% endblock %} + +{% block header %}

Show menus for custom location selection

{% endblock %} + +{% block content %} + +
+ +
+ {% for name, id in types %} + + {% endfor %} +
+
+ {% for status_value in status_values %} + + {% endfor %} +
+
+ {% for location in locations %} + + {% endfor %} +
+ +
+ +
+ {% for day, menus in menu_data.items %} +
+

{{ day }}

+ + {% for menu in menus %} + + {% endfor %} + +
+ {% endfor %} +
+ +{% endblock %} diff --git a/mensaviewer/templates/flensburg.html b/mensaviewer/templates/flensburg.html index 1da86c9..100951c 100644 --- a/mensaviewer/templates/flensburg.html +++ b/mensaviewer/templates/flensburg.html @@ -1,17 +1,18 @@ {% extends 'base.html' %} +{% block title %}Flensburg{% endblock %} + +{% block header %}

Mensen in Flensburg

{% endblock %} + {% block content %} -
+ -
+
{% for name, id in types %} {% endfor %}
@@ -19,25 +20,10 @@ {% for status_value in status_values %} {% endfor %}
-
- {% for location in locations %} - - {% endfor %} -
- New Location
@@ -47,23 +33,21 @@

{{ day }}

{% for menu in menus %} - {% endfor %} - {% endblock %} diff --git a/mensaviewer/templates/location/delete.html b/mensaviewer/templates/location/delete.html new file mode 100644 index 0000000..ac3307c --- /dev/null +++ b/mensaviewer/templates/location/delete.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} + +
+ {% csrf_token %} +

Are you sure you want to delete "{{ object }}"?

+ {{ form }} + +
+ +{% endblock %} diff --git a/mensaviewer/templates/location/detail.html b/mensaviewer/templates/location/detail.html new file mode 100644 index 0000000..282c154 --- /dev/null +++ b/mensaviewer/templates/location/detail.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% block content %} + +
+

{{ location.name }}

+

City: {{ location.city }}

+

ID: {{ location.mensa_id }}

+ Add News + Add Menu + + +
+ +{% endblock %} diff --git a/mensaviewer/templates/location/edit.html b/mensaviewer/templates/location/edit.html new file mode 100644 index 0000000..f56eaaf --- /dev/null +++ b/mensaviewer/templates/location/edit.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} + +{% block content %} + +
+ {% csrf_token %} + {{ form }} + +
+ +{% endblock %} diff --git a/mensaviewer/templates/location/list.html b/mensaviewer/templates/location/list.html new file mode 100644 index 0000000..f074165 --- /dev/null +++ b/mensaviewer/templates/location/list.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} + +{% block content %} + +

Locations

+ + + + + + + + + + + + + {% for location in object_list %} + + + + + + + + {% endfor %} +
+ New Location +
NameCityID
{{ location.name }}{{ location.city }}{{ location.mensa_id }}EditDelete
+ +{% endblock %} diff --git a/mensaviewer/templates/menu/delete.html b/mensaviewer/templates/menu/delete.html new file mode 100644 index 0000000..ac3307c --- /dev/null +++ b/mensaviewer/templates/menu/delete.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} + +
+ {% csrf_token %} +

Are you sure you want to delete "{{ object }}"?

+ {{ form }} + +
+ +{% endblock %} diff --git a/mensaviewer/templates/menu/detail.html b/mensaviewer/templates/menu/detail.html new file mode 100644 index 0000000..729c4f5 --- /dev/null +++ b/mensaviewer/templates/menu/detail.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block content %} + +
+

{{ menu.name }}

+

Art: {{ menu.art }}

+

Price for Students: {{ menu.get_price.Student }}

+

Price for Employees: {{ menu.get_price.Employee }}

+

Price for Guests: {{ menu.get_price.Guest }}

+

Allergens: {{ menu.get_allergens }}

+

Types: {{ menu.get_types }}

+

Rating: {{ menu.likes }}

+

Location and Date: {{ menu.location.city }} {{ menu.location.name }} | {{ menu.day }}

+ Like + Dislike + Comment + +
+ +{% endblock %} diff --git a/mensaviewer/templates/menu/edit.html b/mensaviewer/templates/menu/edit.html new file mode 100644 index 0000000..f56eaaf --- /dev/null +++ b/mensaviewer/templates/menu/edit.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} + +{% block content %} + +
+ {% csrf_token %} + {{ form }} + +
+ +{% endblock %} diff --git a/mensaviewer/templates/menu/list.html b/mensaviewer/templates/menu/list.html new file mode 100644 index 0000000..aeb8579 --- /dev/null +++ b/mensaviewer/templates/menu/list.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} + +{% block content %} + +

Menus

+ + + + + + + + + + + + + {% for menu in object_list %} + + + + + + + + {% endfor %} + + +{% endblock %} diff --git a/mensaviewer/templates/news/delete.html b/mensaviewer/templates/news/delete.html new file mode 100644 index 0000000..ac3307c --- /dev/null +++ b/mensaviewer/templates/news/delete.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} + +
+ {% csrf_token %} +

Are you sure you want to delete "{{ object }}"?

+ {{ form }} + +
+ +{% endblock %} diff --git a/mensaviewer/templates/news/detail.html b/mensaviewer/templates/news/detail.html new file mode 100644 index 0000000..73b2198 --- /dev/null +++ b/mensaviewer/templates/news/detail.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} + +
+

By {{ newsarticle.author }}

+

Timestamp: {{ newsarticle.timestamp }}

+

Location: {{ newsarticle.location.name }}

+

Text: {{ newsarticle.text }}

+
+ +{% endblock %} diff --git a/mensaviewer/templates/news/edit.html b/mensaviewer/templates/news/edit.html new file mode 100644 index 0000000..f56eaaf --- /dev/null +++ b/mensaviewer/templates/news/edit.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} + +{% block content %} + +
+ {% csrf_token %} + {{ form }} + +
+ +{% endblock %} diff --git a/mensaviewer/templates/news/list.html b/mensaviewer/templates/news/list.html new file mode 100644 index 0000000..725ffdb --- /dev/null +++ b/mensaviewer/templates/news/list.html @@ -0,0 +1,33 @@ +{% extends 'base.html' %} + +{% block content %} + +

News Articles

+ + + + + + + + + + + + + + {% for newsarticle in object_list %} + + + + + + + + + {% endfor %} +
+ Add News Article +
TitleAuthorTimestampMenu
{{ newsarticle.title }}{{ newsarticle.author }}{{ newsarticle.timestamp }}{{ newsarticle.location.city }} - {{ newsarticle.location.name }}EditDelete
+ +{% endblock %} diff --git a/mensaviewer/urls.py b/mensaviewer/urls.py index 825038f..133137b 100644 --- a/mensaviewer/urls.py +++ b/mensaviewer/urls.py @@ -3,10 +3,46 @@ from . import views app_name = 'mensaviewer' urlpatterns = [ - path('', views.home, name='home'), + # routes for overview + path('', views.flensburg, name='flensburg'), + path('custom-location', views.custom, name='custom'), + + # raw create routes + path('add-location/', views.LocationCreateView.as_view(), name='location_create'), + path('add-news-article/', views.NewsArticleCreateView.as_view(), name='news_create'), + path('add-menu/', views.MenuCreateView.as_view(), name='menu_create'), + path('add-comment/', views.CommentCreateView.as_view(), name='comment_create'), + + # relation-dependent create routes + path('location//add-news-article/', views.NewsArticleCreateView.as_view(), name='news_add'), + path('location//add-menu/', views.MenuCreateView.as_view(), name='menu_add'), + path('menu//add-comment/', views.CommentCreateView.as_view(), name='comment_add'), + + # list-view routes + path('locations/', views.LocationListView.as_view(), name='location_list'), + path('news-articles/', views.NewsArticleListView.as_view(), name='news_list'), + path('menus/', views.MenuListView.as_view(), name='menu_list'), + path('comments/', views.CommentListView.as_view(), name='comment_list'), + + # detail-view routes + path('location//', views.LocationDetailView.as_view(), name='location_detail'), + path('news-article//', views.NewsArticleDetailView.as_view(), name='news_detail'), path('menu//', views.MenuDetailView.as_view(), name='menu_detail'), + path('comment//', views.CommentDetailView.as_view(), name='comment_detail'), + + # update-view routes + path('location//edit/', views.LocationUpdateView.as_view(), name='location_update'), + path('news-artice//edit/', views.NewsArticleUpdateView.as_view(), name='news_update'), + path('menu//edit/', views.MenuUpdateView.as_view(), name='menu_update'), + path('comment//edit/', views.CommentUpdateView.as_view(), name='comment_update'), + + # delete-view routes + path('location//delete/', views.LocationDeleteView.as_view(), name='location_delete'), + path('news-artice//delete/', views.NewsArticleDeleteView.as_view(), name='news_delete'), + path('menu//delete/', views.MenuDeleteView.as_view(), name='menu_delete'), + path('comment//delete/', views.CommentDeleteView.as_view(), name='comment_delete'), + + # like / dislike routes path('menu//like/', views.like, name='like'), path('menu//dislike/', views.dislike, name='dislike'), - path('menu//comment/', views.CommentCreateView.as_view(), name='comment'), - path('location/create/', views.LocationCreateView.as_view(), name='location_create') ] diff --git a/mensaviewer/views.py b/mensaviewer/views.py index d11481e..683ef11 100644 --- a/mensaviewer/views.py +++ b/mensaviewer/views.py @@ -1,6 +1,7 @@ -from django.views.generic import DetailView -from django.views.generic.edit import CreateView +from django.views.generic import DetailView, ListView +from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.shortcuts import render, redirect, reverse +from django.urls import reverse_lazy from .models import * from .helpers import * @@ -8,16 +9,36 @@ from .forms import * from .data import * -def home(request): +def flensburg(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) + return redirect(reverse('mensaviewer:flensburg') + '?' + filter_query) + + checked_types = request.GET.getlist('type') + status = request.GET.get('status') + selected_locations = Location.objects.filter(mensa_id__in=[1, 6]) + context = { + 'types': [(TYPES[type], type) for type in TYPES], + 'checked_types': checked_types, + 'status_values': STATUS_VALUES, + 'status': status, + 'locations': Location.objects.all(), + '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 custom(request): + filter_query = request.COOKIES.get('filter_query', '') + if len(request.GET.getlist('filterform')) == 0 and filter_query != '': + return redirect(reverse('mensaviewer:custom') + '?' + 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, @@ -27,8 +48,7 @@ def home(request): 'selected_locations': selected_location_ids, 'menu_data': load_data(selected_locations, checked_types, status), } - - response = render(request, "flensburg.html", context) + response = render(request, "custom.html", context) response.set_cookie('filter_query', request.GET.urlencode()) return response @@ -38,7 +58,7 @@ def like(request, pk): menu = Menu.objects.get(pk=pk) menu.likes += 1 menu.save() - return redirect(reverse('mensaviewer:home') + '?' + request.GET.get('next')) + return redirect(request.GET.get('next')) def dislike(request, pk): @@ -46,30 +66,155 @@ def dislike(request, pk): menu = Menu.objects.get(pk=pk) menu.likes -= 1 menu.save() - return redirect(reverse('mensaviewer:home') + '?' + request.GET.get('next')) + return redirect(request.GET.get('next')) -class MenuDetailView(DetailView): - model = Menu - template_name = "menu_detail.html" - queryset = Menu.objects.all() class LocationCreateView(CreateView): model = Location + template_name = "location/edit.html" fields = '__all__' - template_name = "create_location.html" - success_url = "/" + success_url = reverse_lazy("mensaviewer:location_list") + + +class LocationUpdateView(UpdateView): + model = Location + template_name = "location/edit.html" + fields = '__all__' + success_url = reverse_lazy("mensaviewer:location_list") + + +class LocationDeleteView(DeleteView): + model = Location + template_name = "location/delete.html" + success_url = reverse_lazy("mensaviewer:location_list") + + +class LocationDetailView(DetailView): + model = Location + template_name = "location/detail.html" + + +class LocationListView(ListView): + model = Location + template_name = "location/list.html" + + + + +class NewsArticleCreateView(CreateView): + model = NewsArticle + template_name = "news/edit.html" + fields = ['location', 'title', 'author', 'text'] + success_url = reverse_lazy("mensaviewer:news_list") + + def get_initial(self): + if 'pk' in self.kwargs: + return { 'location': get_object_or_404(Location, pk=self.kwargs['pk']) } + else: + return { 'location': None } + + +class NewsArticleUpdateView(UpdateView): + model = NewsArticle + template_name = "news/edit.html" + fields = ['location', 'title', 'author', 'text'] + success_url = reverse_lazy("mensaviewer:news_list") + + +class NewsArticleDeleteView(DeleteView): + model = NewsArticle + template_name = "news/delete.html" + success_url = reverse_lazy("mensaviewer:news_list") + + +class NewsArticleDetailView(DetailView): + model = NewsArticle + template_name = "news/detail.html" + + +class NewsArticleListView(ListView): + model = NewsArticle + template_name = "news/list.html" + + + + +class MenuCreateView(CreateView): + model = Menu + form_class = MenuForm + template_name = "menu/edit.html" + success_url = reverse_lazy("mensaviewer:menu_list") + + def get_initial(self): + if 'pk' in self.kwargs: + return { 'location': get_object_or_404(Location, pk=self.kwargs['pk']) } + else: + return { 'location': None } + + +class MenuUpdateView(UpdateView): + model = Menu + form_class = MenuForm + template_name = "menu/edit.html" + success_url = reverse_lazy("mensaviewer:menu_list") + + def get_initial(self): + menu = get_object_or_404(Menu, pk=self.kwargs['pk']) + print(menu.types) + return { 'types': menu.types } + + +class MenuDeleteView(DeleteView): + model = Menu + template_name = "menu/delete.html" + success_url = reverse_lazy("mensaviewer:menu_list") + + +class MenuDetailView(DetailView): + model = Menu + template_name = "menu/detail.html" + + +class MenuListView(ListView): + model = Menu + template_name = "menu/list.html" + + class CommentCreateView(CreateView): model = Comment - fields = ['username', 'comment'] - template_name = "comment.html" - success_url = '/menu/{pk}/' + fields = ['menu', 'author', 'text'] + template_name = "comment/edit.html" + success_url = '/menu/{menu_id}/' - 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) + def get_initial(self): + if 'pk' in self.kwargs: + return { 'menu': get_object_or_404(Menu, pk=self.kwargs['pk']) } + else: + return { 'menu': None } + + +class CommentUpdateView(UpdateView): + model = Comment + fields = ['author', 'text'] + template_name = "comment/edit.html" + success_url = '/menu/{menu_id}/' + + +class CommentDeleteView(DeleteView): + model = Comment + template_name = "comment/delete.html" + success_url = '/menu/{menu_id}/' + + +class CommentDetailView(DetailView): + model = Comment + template_name = "comment/detail.html" + + +class CommentListView(ListView): + model = Comment + template_name = "comment/list.html" diff --git a/static/base.css b/static/base.css index fb859d9..baa8f88 100644 --- a/static/base.css +++ b/static/base.css @@ -1,15 +1,170 @@ * { - font-family: monospace; + font-family: Tahoma; box-sizing: border-box; - outline: 1px solid green; - background-color: rgba(16, 16, 16, 0.1) } body { margin: 0; + background-color: #1D1D28; + color: #EEEDFF; +} + +h1, h2, h3 { + margin: 0; + padding: 1em; +} + +header { + padding-left: 12vw; + box-shadow: 0px 0px 32px rgba(0, 0, 0, 0.5); +} + +main { + display: flex; + background-color: #3E3949; +} + +nav { + border-right: 1px solid rgba(0, 0, 0, 0.2); + display: flex; + flex-direction: column; + width: 16vw; + min-width: 16em; + padding-top: 4em; +} + +nav a { + text-align: center; + font-size: 18px; + padding: 0.6em; +} + +nav a:hover { + transition: 250ms; + background-color: rgba(0, 0, 0, 0.2); +} + +nav hr { + width: 16em; + opacity: 0.1; +} + +a { + text-decoration: none; + color: #EEEDFF; +} + +a:hover { + transition: 250ms; + color: #FFEFCA; +} + +a:active { + color: #CEA716; +} + +form { + padding: 1em; + margin: 0.5em; + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: 0.5em; + box-shadow: inset 0px 0px 1em rgba(0, 0, 0, 0.2); +} + +table { + width: 100%; + padding: 1em; + margin: 0.5em; + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: 0.5em; + box-shadow: inset 0px 0px 1em rgba(0, 0, 0, 0.2); +} + +th { + padding: 0.5em; + color: #CEA716; + text-align: left; +} + +td { + padding: 0.5em; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 0.2em; + max-width: 30vw; +} + +.fit-width { + width: 1%; +} + +.content { + margin-left: 4vw; + margin-right: 4vw; + flex: 1; +} + +.centered-td { + text-align: center; + padding: 1em; +} + +.btn { + padding: 0.4em; + background-color: rgba(0, 0, 0, 0.2); + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: 0.5em; +} + +.model-form { + display: flex; + flex-direction: column; +} + +.model-form input, .model-form textarea, .model-form select, button { + margin-top: 0.5em; + margin-bottom: 1em; + padding: 0.5em; + background-color: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(0, 0, 0, 1); + border-radius: 0.5em; + color: #EEEDFF; +} + +.filter-form { + display: flex; + flex-direction: column; +} + +.filter-form .type-filter, .filter-form .status-filter, .filter-form .location-filter { + display: flex; + flex-direction: row; +} + +.filter-form label { + background-color: rgba(0, 0, 0, 0.2); + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: 0.3em; + padding: 0.3em; + margin: 0.5em; + text-align: right; +} + +input[type="submit"], button { + cursor: pointer; +} + +input[type="submit"]:active, button:active { + transition: 200ms; + background-color: rgba(0, 0, 0, 0.7); + cursor: pointer; } .container { + padding: 0.3em; + margin: 0.3em; + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: 0.5em; + box-shadow: inset 0px 0px 1em rgba(0, 0, 0, 0.2); display: flex; flex-flow: row wrap; } @@ -20,10 +175,31 @@ body { } .menu { + padding: 1em; + margin: 0.2em; + border-radius: 0.5em; + box-shadow: inset 0px 0px 1em rgba(0, 0, 0, 0.2); + background-color: rgba(0, 0, 0, 0.2); display: flex; flex-direction: column; } +.menu * { + margin: 0; +} + +.menu hr { + opacity: 0.2; +} + +.menu p { + font-size: 12px; +} + +.menu a { + display: inline-block; +} + .a-mensa { background-color: rgba(16, 64, 16, 0.2); } From bb1bf12de330090bed2d9a4a71852fe600da541d Mon Sep 17 00:00:00 2001 From: adb-sh Date: Sun, 8 Jan 2023 21:00:02 +0100 Subject: [PATCH 5/7] add deployment --- .drone.yml | 39 +++++++++++++++++++++++++++++++++++++++ Dockerfile | 10 ++++++++++ docker-compose.prod.yml | 20 ++++++++++++++++++++ requirements.txt | 2 ++ 4 files changed, 71 insertions(+) create mode 100644 .drone.yml create mode 100644 Dockerfile create mode 100644 docker-compose.prod.yml create mode 100644 requirements.txt diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..b770e26 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,39 @@ +--- +kind: pipeline +type: docker +name: build + +steps: + - name: docker + image: plugins/docker + settings: + registry: docker.cybre.town + repo: docker.cybre.town/adb/webprog-homework + tags: "latest" + username: + from_secret: docker_username + password: + from_secret: docker_password + +--- +kind: pipeline +type: docker +name: deploy + +depends_on: + - build + +steps: + - name: pull and deploy + image: appleboy/drone-ssh:linux-amd64 + settings: + host: + from_secret: ssh_host + username: + from_secret: ssh_user_name + key: + from_secret: ssh_private_key + script: | + cd /media/docker/webprog-homework + docker compose -f docker-compose.prod.yml -p webprog-homework pull -q + docker compose -f docker-compose.prod.yml -p webprog-homework up -d diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e026cc4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3-alpine + +COPY ./ /usr/src/app +WORKDIR /usr/src/app + +RUN pip install -r requirements.txt +RUN python ./manage.py migrate + +EXPOSE 8000 +CMD [ "python", "./manage.py", "runserver", "0.0.0.0:8000" ] diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..0a11f8a --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,20 @@ +version: '3' + +networks: + web: + external: true + +services: + frontend: + image: docker.cybre.town/adb/webprog-homework + labels: + - "traefik.enable=true" + - "traefik.http.routers.webprog-homework.rule=Host(`webprog-homework.deploy.cat`)" + - "traefik.http.routers.webprog-homework.entrypoints=https" + - "traefik.http.services.webprog-homework.loadbalancer.server.port=8000" + - "traefik.http.routers.webprog-homework.tls.certresolver=mytlschallenge" + - "traefik.docker.network=web" + volumes: + - ./db.sqlite:/usr/src/app/db.sqlite + networks: + - web diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c9736c8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Django==4.1.3 +requests==2.28.1 \ No newline at end of file From b03b1d4d985b1865c5a7df11e61eebeb624c2875 Mon Sep 17 00:00:00 2001 From: adb-sh Date: Sun, 8 Jan 2023 21:07:42 +0100 Subject: [PATCH 6/7] add allowed host --- webprog/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webprog/settings.py b/webprog/settings.py index a03a411..a8adb0c 100644 --- a/webprog/settings.py +++ b/webprog/settings.py @@ -25,7 +25,7 @@ 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'] +ALLOWED_HOSTS = ['127.0.0.1', '192.168.178.36', 'webprog-homework.deploy.cat'] # Application definition From 4b07f346a01f6ef661f19c8e03f7042651bdcf44 Mon Sep 17 00:00:00 2001 From: Jannes Hikmat Date: Sun, 8 Jan 2023 21:16:45 +0100 Subject: [PATCH 7/7] =?UTF-8?q?templates,=20css=20&=20views=20nochmal=20?= =?UTF-8?q?=C3=BCberarbeitet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mensaviewer/templates/comment/delete.html | 5 ++ mensaviewer/templates/comment/detail.html | 12 +++- mensaviewer/templates/comment/edit.html | 5 ++ mensaviewer/templates/comment/list.html | 11 +++- mensaviewer/templates/custom.html | 20 ++++-- mensaviewer/templates/flensburg.html | 14 ++-- mensaviewer/templates/location/delete.html | 5 ++ mensaviewer/templates/location/detail.html | 66 ++++++++++++++----- mensaviewer/templates/location/edit.html | 5 ++ mensaviewer/templates/location/list.html | 7 +- mensaviewer/templates/menu/delete.html | 5 ++ mensaviewer/templates/menu/detail.html | 77 +++++++++++++++++----- mensaviewer/templates/menu/edit.html | 5 ++ mensaviewer/templates/menu/list.html | 7 +- mensaviewer/templates/news/delete.html | 5 ++ mensaviewer/templates/news/detail.html | 19 ++++-- mensaviewer/templates/news/edit.html | 5 ++ mensaviewer/templates/news/list.html | 7 +- mensaviewer/views.py | 6 +- static/base.css | 39 +++++++---- 20 files changed, 251 insertions(+), 74 deletions(-) diff --git a/mensaviewer/templates/comment/delete.html b/mensaviewer/templates/comment/delete.html index ac3307c..d259171 100644 --- a/mensaviewer/templates/comment/delete.html +++ b/mensaviewer/templates/comment/delete.html @@ -1,5 +1,10 @@ {% extends 'base.html' %} + +{% block title %}Delete Comment{% endblock %} + +{% block header %}

Delete comment by {{ object.author }}

{% endblock %} + {% block content %}
diff --git a/mensaviewer/templates/comment/detail.html b/mensaviewer/templates/comment/detail.html index 103231e..7928c0b 100644 --- a/mensaviewer/templates/comment/detail.html +++ b/mensaviewer/templates/comment/detail.html @@ -1,12 +1,18 @@ {% extends 'base.html' %} + +{% block title %}Comment{% endblock %} + +{% block header %}

Comment by {{ comment.author }}

{% endblock %} + {% block content %}
-

By {{ comment.author }}

-

Timestamp: {{ comment.timestamp }}

Menu: {{ comment.menu.name }}

-

Text: {{ comment.text }}

+

Timestamp: {{ comment.timestamp }}

+
+

Text:

+

{{ comment.text }}

{% endblock %} diff --git a/mensaviewer/templates/comment/edit.html b/mensaviewer/templates/comment/edit.html index f56eaaf..52d062c 100644 --- a/mensaviewer/templates/comment/edit.html +++ b/mensaviewer/templates/comment/edit.html @@ -1,5 +1,10 @@ {% extends 'base.html' %} + +{% block title %}Edit Comment{% endblock %} + +{% block header %}

Edit Comment

{% endblock %} + {% block content %} diff --git a/mensaviewer/templates/comment/list.html b/mensaviewer/templates/comment/list.html index d1857bd..5b6a516 100644 --- a/mensaviewer/templates/comment/list.html +++ b/mensaviewer/templates/comment/list.html @@ -1,8 +1,11 @@ {% extends 'base.html' %} -{% block content %} -

Comments

+{% block title %}Comments{% endblock %} + +{% block header %}

Comments

{% endblock %} + +{% block content %} @@ -11,6 +14,7 @@ + @@ -19,8 +23,9 @@ {% for comment in object_list %} + - + diff --git a/mensaviewer/templates/custom.html b/mensaviewer/templates/custom.html index ce1808e..d187c56 100644 --- a/mensaviewer/templates/custom.html +++ b/mensaviewer/templates/custom.html @@ -16,6 +16,7 @@ {% endfor %} +
{% for status_value in status_values %} {% endfor %}
+
{% for location in locations %}
Text Author Timestamp Menu
{{ comment.text }} {{ comment.author }}{{ comment.timestamp }}{{ comment.timestamp }} {{ comment.menu }} Edit Delete
+ + + + + + + + + {% for newsarticle in location.news_articles %} + + + + + + + + + {% endfor %} +
TitleAuthorTimestampMenu
{{ newsarticle.title }}{{ newsarticle.author }}{{ newsarticle.timestamp }}{{ newsarticle.location.city }} - {{ newsarticle.location.name }}EditDelete
diff --git a/mensaviewer/templates/location/edit.html b/mensaviewer/templates/location/edit.html index f56eaaf..0f9517c 100644 --- a/mensaviewer/templates/location/edit.html +++ b/mensaviewer/templates/location/edit.html @@ -1,5 +1,10 @@ {% extends 'base.html' %} + +{% block title %}Edit Location{% endblock %} + +{% block header %}

Edit location {{ object.name }}

{% endblock %} + {% block content %} diff --git a/mensaviewer/templates/location/list.html b/mensaviewer/templates/location/list.html index f074165..2527cd0 100644 --- a/mensaviewer/templates/location/list.html +++ b/mensaviewer/templates/location/list.html @@ -1,8 +1,11 @@ {% extends 'base.html' %} -{% block content %} -

Locations

+{% block title %}Locations{% endblock %} + +{% block header %}

Locations

{% endblock %} + +{% block content %} diff --git a/mensaviewer/templates/menu/delete.html b/mensaviewer/templates/menu/delete.html index ac3307c..6436971 100644 --- a/mensaviewer/templates/menu/delete.html +++ b/mensaviewer/templates/menu/delete.html @@ -1,5 +1,10 @@ {% extends 'base.html' %} + +{% block title %}Delete Menu{% endblock %} + +{% block header %}

Delete menu {{ object.name }}

{% endblock %} + {% block content %} diff --git a/mensaviewer/templates/menu/detail.html b/mensaviewer/templates/menu/detail.html index 729c4f5..3c6e5f9 100644 --- a/mensaviewer/templates/menu/detail.html +++ b/mensaviewer/templates/menu/detail.html @@ -1,27 +1,68 @@ {% extends 'base.html' %} + +{% block title %}{{ menu.name }}{% endblock %} + +{% block header %}

{{ menu.name }}

{% endblock %} + {% block content %}
-

{{ menu.name }}

-

Art: {{ menu.art }}

-

Price for Students: {{ menu.get_price.Student }}

-

Price for Employees: {{ menu.get_price.Employee }}

-

Price for Guests: {{ menu.get_price.Guest }}

-

Allergens: {{ menu.get_allergens }}

-

Types: {{ menu.get_types }}

-

Rating: {{ menu.likes }}

-

Location and Date: {{ menu.location.city }} {{ menu.location.name }} | {{ menu.day }}

- Like - Dislike - Comment +

+ Like + Dislike + Comment +

+

Prices

+
+ + + + + + + + + + +
StudentsEmployeesGuests
{{ menu.get_price.Student }}{{ menu.get_price.Employee }}{{ menu.get_price.Guest }}
+

Art

+

{{ menu.art }}

+
+

Allergens

+

{{ menu.get_allergens }}

+
+

Types

+

{{ menu.get_types }}

+
+

Rating

+

{{ menu.likes }}

+
+

Location and Date

+

{{ menu.location.city }} {{ menu.location.name }} | {{ menu.day }}

+
diff --git a/mensaviewer/templates/menu/edit.html b/mensaviewer/templates/menu/edit.html index f56eaaf..a396609 100644 --- a/mensaviewer/templates/menu/edit.html +++ b/mensaviewer/templates/menu/edit.html @@ -1,5 +1,10 @@ {% extends 'base.html' %} + +{% block title %}Edit Menu{% endblock %} + +{% block header %}

Edit Menu {{ object.name }}

{% endblock %} + {% block content %} diff --git a/mensaviewer/templates/menu/list.html b/mensaviewer/templates/menu/list.html index aeb8579..86a3441 100644 --- a/mensaviewer/templates/menu/list.html +++ b/mensaviewer/templates/menu/list.html @@ -1,8 +1,11 @@ {% extends 'base.html' %} -{% block content %} -

Menus

+{% block title %}Menus{% endblock %} + +{% block header %}

Menus

{% endblock %} + +{% block content %} diff --git a/mensaviewer/templates/news/delete.html b/mensaviewer/templates/news/delete.html index ac3307c..54f4d5e 100644 --- a/mensaviewer/templates/news/delete.html +++ b/mensaviewer/templates/news/delete.html @@ -1,5 +1,10 @@ {% extends 'base.html' %} + +{% block title %}Delete News{% endblock %} + +{% block header %}

Delete news article {{ object.title }}

{% endblock %} + {% block content %} diff --git a/mensaviewer/templates/news/detail.html b/mensaviewer/templates/news/detail.html index 73b2198..fab0bb5 100644 --- a/mensaviewer/templates/news/detail.html +++ b/mensaviewer/templates/news/detail.html @@ -1,12 +1,23 @@ {% extends 'base.html' %} + +{% block title %}{{ newsarticle.title }}{% endblock %} + +{% block header %}

{{ newsarticle.title }}

{% endblock %} + {% block content %}
-

By {{ newsarticle.author }}

-

Timestamp: {{ newsarticle.timestamp }}

-

Location: {{ newsarticle.location.name }}

-

Text: {{ newsarticle.text }}

+

Written by {{ newsarticle.author }}

+
+

Timestamp

+

{{ newsarticle.timestamp }}

+
+

Location

+

{{ newsarticle.location.name }}

+
+

Text

+

{{ newsarticle.text }}

{% endblock %} diff --git a/mensaviewer/templates/news/edit.html b/mensaviewer/templates/news/edit.html index f56eaaf..e91b47d 100644 --- a/mensaviewer/templates/news/edit.html +++ b/mensaviewer/templates/news/edit.html @@ -1,5 +1,10 @@ {% extends 'base.html' %} + +{% block title %}Edit News Article{% endblock %} + +{% block header %}

Edit news article {{ object.title }}

{% endblock %} + {% block content %} diff --git a/mensaviewer/templates/news/list.html b/mensaviewer/templates/news/list.html index 725ffdb..90fbd2d 100644 --- a/mensaviewer/templates/news/list.html +++ b/mensaviewer/templates/news/list.html @@ -1,8 +1,11 @@ {% extends 'base.html' %} -{% block content %} -

News Articles

+{% block title %}News Articles{% endblock %} + +{% block header %}

News Articles

{% endblock %} + +{% block content %} diff --git a/mensaviewer/views.py b/mensaviewer/views.py index 683ef11..e4a1ec5 100644 --- a/mensaviewer/views.py +++ b/mensaviewer/views.py @@ -16,13 +16,15 @@ def flensburg(request): checked_types = request.GET.getlist('type') status = request.GET.get('status') - selected_locations = Location.objects.filter(mensa_id__in=[1, 6]) + selected_locations = Location.objects.filter(mensa_id__in=[7, 14]) + if not selected_locations.exists(): + Location(city="Flensburg", name="Hauptmensa", mensa_id=7).save() + Location(city="Flensburg", name="B-Mensa", mensa_id=14).save() context = { 'types': [(TYPES[type], type) for type in TYPES], 'checked_types': checked_types, 'status_values': STATUS_VALUES, 'status': status, - 'locations': Location.objects.all(), 'menu_data': load_data(selected_locations, checked_types, status), } response = render(request, "flensburg.html", context) diff --git a/static/base.css b/static/base.css index baa8f88..a153387 100644 --- a/static/base.css +++ b/static/base.css @@ -9,6 +9,10 @@ body { color: #EEEDFF; } +hr { + opacity: 0.2; +} + h1, h2, h3 { margin: 0; padding: 1em; @@ -46,7 +50,6 @@ nav a:hover { nav hr { width: 16em; - opacity: 0.1; } a { @@ -149,6 +152,10 @@ td { text-align: right; } +.filter-form hr { + width: 100%; +} + input[type="submit"], button { cursor: pointer; } @@ -174,36 +181,46 @@ input[type="submit"]:active, button:active { flex-flow: column; } -.menu { +.related-items { + margin: 2em; +} + +.comment { + border-radius: 0.2em; + border: 1px solid rgba(0, 0, 0, 0.3); + margin: 0.5em; + padding: 1em; +} + +.menu, .news-article { padding: 1em; margin: 0.2em; - border-radius: 0.5em; + margin-bottom: 0.8em; + border-radius: 0.2em; + border: 1px solid rgba(0, 0, 0, 0.3); box-shadow: inset 0px 0px 1em rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2); display: flex; flex-direction: column; } -.menu * { - margin: 0; -} - .menu hr { - opacity: 0.2; + width: 100%; } .menu p { font-size: 12px; + margin: 0; } .menu a { - display: inline-block; + display: inline; } .a-mensa { - background-color: rgba(16, 64, 16, 0.2); + background-color: rgba(40, 29, 40, 0.7); } .b-mensa { - background-color: rgba(16, 16, 64, 0.2); + background-color: rgba(29, 40, 40, 0.7); }