Merge branch 'master' into app.wiai.de
This commit is contained in:
commit
9f20cc5fe3
@ -7,6 +7,7 @@ WORKDIR /app
|
||||
EXPOSE 80
|
||||
VOLUME ["/app/data"]
|
||||
VOLUME ["/app/media"]
|
||||
VOLUME ["/app/log"]
|
||||
ENTRYPOINT ["python3", "manage.py"]
|
||||
ADD ["ofu_app", "/app"]
|
||||
CMD ["runserver", "0.0.0.0:80"]
|
||||
|
||||
@ -3,9 +3,11 @@ ADD ["ofu_app/requirements.txt", "/requirements.txt"]
|
||||
RUN apk upgrade --update && \
|
||||
apk add --update python3 py3-pillow py3-lxml py3-psycopg2 && \
|
||||
pip3 install -r /requirements.txt && rm /requirements.txt
|
||||
EXPOSE 80
|
||||
WORKDIR /app
|
||||
VOLUME ["/app"]
|
||||
VOLUME ["/app/data"]
|
||||
VOLUME ["/app/media"]
|
||||
VOLUME ["/app/log"]
|
||||
ENTRYPOINT ["python3", "manage.py"]
|
||||
CMD ["runserver", "0.0.0.0:80"]
|
||||
|
||||
@ -9,6 +9,7 @@ services:
|
||||
volumes:
|
||||
- ./data/data:/data
|
||||
- ./data/media:/media
|
||||
- ./log:/log
|
||||
- ./ofu_app/:/app
|
||||
env_file:
|
||||
- docker.env
|
||||
|
||||
13
ofu_app/apps/donar/management/commands/delete_lectures.py
Normal file
13
ofu_app/apps/donar/management/commands/delete_lectures.py
Normal file
@ -0,0 +1,13 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from apps.donar.models import Room
|
||||
from apps.donar.utils import migrate_data_lectures
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Imports Lectures from UnivIS PRG. Requires Room import"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
pass
|
||||
|
||||
def handle(self, *args, **options):
|
||||
migrate_data_lectures.delete()
|
||||
13
ofu_app/apps/donar/management/commands/delete_rooms.py
Normal file
13
ofu_app/apps/donar/management/commands/delete_rooms.py
Normal file
@ -0,0 +1,13 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from apps.donar.models import Room
|
||||
from apps.donar.utils import migrate_data_rooms
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Imports Rooms from Univis PRG"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
pass
|
||||
|
||||
def handle(self, *args, **options):
|
||||
migrate_data_rooms.delete()
|
||||
18
ofu_app/apps/donar/migrations/0005_auto_20180401_1136.py
Normal file
18
ofu_app/apps/donar/migrations/0005_auto_20180401_1136.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.0.1 on 2018-04-01 11:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('donar', '0004_auto_20180117_0137'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='short',
|
||||
field=models.CharField(max_length=256),
|
||||
),
|
||||
]
|
||||
28
ofu_app/apps/donar/migrations/0006_auto_20180401_1139.py
Normal file
28
ofu_app/apps/donar/migrations/0006_auto_20180401_1139.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Generated by Django 2.0.1 on 2018-04-01 11:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('donar', '0005_auto_20180401_1136'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='lecturer_id',
|
||||
field=models.CharField(max_length=512),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='univis_id',
|
||||
field=models.CharField(max_length=512, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='univis_ref',
|
||||
field=models.CharField(max_length=512, unique=True),
|
||||
),
|
||||
]
|
||||
18
ofu_app/apps/donar/migrations/0007_auto_20180401_1140.py
Normal file
18
ofu_app/apps/donar/migrations/0007_auto_20180401_1140.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.0.1 on 2018-04-01 11:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('donar', '0006_auto_20180401_1139'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='name',
|
||||
field=models.CharField(max_length=512),
|
||||
),
|
||||
]
|
||||
28
ofu_app/apps/donar/migrations/0008_auto_20180401_1142.py
Normal file
28
ofu_app/apps/donar/migrations/0008_auto_20180401_1142.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Generated by Django 2.0.1 on 2018-04-01 11:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('donar', '0007_auto_20180401_1140'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='lecturer_id',
|
||||
field=models.CharField(max_length=1024),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='univis_id',
|
||||
field=models.CharField(max_length=1024, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='univis_ref',
|
||||
field=models.CharField(max_length=1024, unique=True),
|
||||
),
|
||||
]
|
||||
33
ofu_app/apps/donar/migrations/0009_auto_20180401_1143.py
Normal file
33
ofu_app/apps/donar/migrations/0009_auto_20180401_1143.py
Normal file
@ -0,0 +1,33 @@
|
||||
# Generated by Django 2.0.1 on 2018-04-01 11:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('donar', '0008_auto_20180401_1142'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='lecturer_id',
|
||||
field=models.CharField(max_length=256),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='short',
|
||||
field=models.CharField(max_length=512),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='univis_id',
|
||||
field=models.CharField(max_length=256, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lecture',
|
||||
name='univis_ref',
|
||||
field=models.CharField(max_length=256, unique=True),
|
||||
),
|
||||
]
|
||||
@ -11,8 +11,8 @@ MAX_COORDS_NAME_LENGTH = 256
|
||||
MAX_COORDS_LENGTH = 256
|
||||
|
||||
MAX_LECTURE_IDS_LENGTH = 256
|
||||
MAX_LECTURE_SHORT_LENGTH = 128
|
||||
MAX_LECTURE_NAME_LENGTH = 256
|
||||
MAX_LECTURE_SHORT_LENGTH = 512
|
||||
MAX_LECTURE_NAME_LENGTH = 512
|
||||
MAX_LECTURE_TYPE_LENGTH = 64
|
||||
|
||||
|
||||
|
||||
@ -4,6 +4,9 @@ import json
|
||||
from pprint import pprint
|
||||
from django.db.utils import IntegrityError
|
||||
from apps.donar.utils.parser import univis_lectures_parser
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# CONFIG Fakultaet
|
||||
FAKULTAET_GuK = "Fakult%E4t%20Geistes-%20und%20Kulturwissenschaften"
|
||||
@ -32,38 +35,35 @@ def writeUnivisLectureTermsInDB(lecture, lecture_obj):
|
||||
if type(lecture['terms']['term']) == list:
|
||||
for term in lecture['terms']['term']:
|
||||
try:
|
||||
term_obj = Lecture_Terms.objects.create()
|
||||
starttime = "00:00"
|
||||
term_obj = Lecture_Terms.objects.create(starttime=starttime)
|
||||
if 'starttime' in term:
|
||||
starttime = term['starttime']
|
||||
term_obj.starttime = datetime.strptime(starttime, "%H:%M")
|
||||
term_obj.save()
|
||||
if 'room' in term:
|
||||
room_id = term['room']['UnivISRef']['@key']
|
||||
term_obj.room = [Room.objects.get(key=room_id)]
|
||||
term_obj.save()
|
||||
term_obj.room.add(Room.objects.get(key=room_id))
|
||||
lecture_obj.term.add(term_obj)
|
||||
except IntegrityError as err:
|
||||
print("ROOM_ID: " + str(room_id))
|
||||
print(err.args)
|
||||
logger.exception(err)
|
||||
|
||||
else:
|
||||
try:
|
||||
term_obj = Lecture_Terms.objects.create()
|
||||
univis_starttime = "00:00"
|
||||
term_obj = Lecture_Terms.objects.create(starttime=univis_starttime)
|
||||
if 'starttime' in lecture['terms']['term']:
|
||||
univis_starttime = lecture['terms']['term']['starttime']
|
||||
term_obj.starttime = datetime.strptime(univis_starttime, '%H:%M')
|
||||
term_obj.save()
|
||||
if 'room' in lecture['terms']['term']:
|
||||
room_id = lecture['terms']['term']['room']['UnivISRef']['@key']
|
||||
pprint("Room: " + room_id)
|
||||
Room.objects.get(key=room_id)
|
||||
term_obj.room = [Room.objects.get(key=room_id)]
|
||||
|
||||
term_obj.room.add(Room.objects.get(key=room_id))
|
||||
term_obj.save()
|
||||
lecture_obj.term.add(term_obj)
|
||||
except IntegrityError as err:
|
||||
print("ROOM_ID: " + str(room_id))
|
||||
print(err.args)
|
||||
logger.exception(err)
|
||||
|
||||
|
||||
def writeUnivisLectureDataInDB(data):
|
||||
@ -91,43 +91,56 @@ def writeUnivisLectureDataInDB(data):
|
||||
lecture_type = lecture['type']
|
||||
if 'dozs' in lecture:
|
||||
lecturer_id = dict(lecture['dozs']['doz']['UnivISRef'])['@key']
|
||||
print("Lecture: " + name)
|
||||
lecture_obj = Lecture.objects.create(univis_ref=key, univis_id=univis_id, name=name, short=short,
|
||||
type=lecture_type, lecturer_id=lecturer_id)
|
||||
writeUnivisLectureTermsInDB(lecture, lecture_obj)
|
||||
lecture_obj.save()
|
||||
logger.info("Lecture: {}".format(lecture_obj.short))
|
||||
except IntegrityError as err:
|
||||
print(err.args)
|
||||
logger.warning('Lecture already exists')
|
||||
# logger.exception(err)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def showStatus(status: str):
|
||||
print(status)
|
||||
pprint("Lectures: " + str(Lecture.objects.count()))
|
||||
pprint("Lecture Terms: " + str(Lecture_Terms.objects.count()))
|
||||
pprint("Room: " + str(Room.objects.count()))
|
||||
return "\nStatus: {status}\n\tLectures: {lectures}\n\tLecture Terms: {lecture_terms}\n\tRoom: {room}".format(
|
||||
status=status,
|
||||
lectures=Lecture.objects.count(),
|
||||
lecture_terms=Lecture_Terms.objects.count(),
|
||||
room=Room.objects.count()
|
||||
)
|
||||
|
||||
|
||||
def delete():
|
||||
lectures = Lecture.objects.all()
|
||||
logger.info("Deleted following Lectures:")
|
||||
for lecture in lectures:
|
||||
logger.info("Lecture: {name}".format(
|
||||
name=lecture.name)
|
||||
)
|
||||
lecture.delete()
|
||||
|
||||
|
||||
def main():
|
||||
# get food jsons
|
||||
showStatus("Start with:")
|
||||
logger.info(showStatus("Start SoWi:"))
|
||||
writeUnivisLectureDataInDB(univis_lectures_parser.parsePage(univis_lectures(FAKULTAET_SoWi)))
|
||||
pprint("----------------------------------------------------------------------------------------")
|
||||
# pprint("----------------------------------------------------------------------------------------")
|
||||
|
||||
showStatus("After SoWi:")
|
||||
logger.info(showStatus("Start GuK:"))
|
||||
writeUnivisLectureDataInDB(univis_lectures_parser.parsePage(univis_lectures(FAKULTAET_GuK)))
|
||||
pprint("----------------------------------------------------------------------------------------")
|
||||
# pprint("----------------------------------------------------------------------------------------")
|
||||
|
||||
showStatus("After GuK:")
|
||||
logger.info(showStatus("Start HuWi:"))
|
||||
writeUnivisLectureDataInDB(univis_lectures_parser.parsePage(univis_lectures(FAKULTAET_HuWi)))
|
||||
pprint("----------------------------------------------------------------------------------------")
|
||||
# pprint("----------------------------------------------------------------------------------------")
|
||||
|
||||
showStatus("After HuWi:")
|
||||
logger.info(showStatus("Start WIAI:"))
|
||||
writeUnivisLectureDataInDB(univis_lectures_parser.parsePage(univis_lectures(FAKULTAET_WIAI)))
|
||||
pprint("----------------------------------------------------------------------------------------")
|
||||
# pprint("----------------------------------------------------------------------------------------")
|
||||
|
||||
showStatus("After WIAI:")
|
||||
logger.info(showStatus("Finished:"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -6,6 +6,10 @@ from apps.donar.models import Room, Lecture_Terms, Lecture
|
||||
from apps.donar.utils.parser import univis_rooms_parser
|
||||
from apps.donar.utils.parser import univis_lectures_parser
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# CONFIG Fakultaet
|
||||
FAKULTAET_GuK = "Fakult%E4t%20Geistes-%20und%20Kulturwissenschaften"
|
||||
FAKULTAET_SoWi = "Fakult%E4t%20Sozial-%20und%20Wirtschaftswissenschaften"
|
||||
@ -73,15 +77,28 @@ def writeUnivisRoomDataInDB(data):
|
||||
if 'description' in room:
|
||||
description = room['description']
|
||||
|
||||
Room.objects.create(key=key, address=address, building_key=building_key, floor=floor, name=name,
|
||||
orgname=orgname, short=short, size=size, description=description)
|
||||
room = Room.objects.create(key=key, address=address, building_key=building_key, floor=floor, name=name,
|
||||
orgname=orgname, short=short, size=size, description=description)
|
||||
room.save()
|
||||
logger.info('ROOM: {}'.format(room.short))
|
||||
except IntegrityError as err:
|
||||
pprint(err.args)
|
||||
logger.warning('Room already exists')
|
||||
|
||||
|
||||
def delete():
|
||||
rooms = Room.objects.all()
|
||||
logger.info("Deleted following Rooms:")
|
||||
for room in rooms:
|
||||
logger.info("Room: {name}".format(
|
||||
name=room.short)
|
||||
)
|
||||
room.delete()
|
||||
|
||||
|
||||
def main():
|
||||
# get food jsons
|
||||
pprint("Begin: Room: " + str(Room.objects.count()))
|
||||
logger.info("Start:\nRoom: {}".format(Room.objects.count()))
|
||||
|
||||
writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms(FAKULTAET_GuK)))
|
||||
writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms(FAKULTAET_SoWi)))
|
||||
writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms(FAKULTAET_HuWi)))
|
||||
@ -102,7 +119,7 @@ def main():
|
||||
writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms_loc("d")))
|
||||
writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms_loc("x")))
|
||||
|
||||
pprint("Now: Room: " + str(Room.objects.count()))
|
||||
logger.info("Finished:\nRoom: {}".format(Room.objects.count()))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -7,6 +7,9 @@ from apps.events.utils.parser import univis_eventpage_parser
|
||||
from apps.events.utils.parser import fekide_eventpage_parser
|
||||
|
||||
from apps.events.models import Event, Location
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
UNIVIS_CATEGORY = 'Univis'
|
||||
|
||||
@ -20,32 +23,17 @@ UNIVIS_RPG_WIAI = "http://univis.uni-bamberg.de/prg?search=events&department=Fak
|
||||
def writeFekideDataInDB(data):
|
||||
for date in data['dates']:
|
||||
for event in date['events']:
|
||||
try:
|
||||
Location.objects.create(name=event['location'])
|
||||
except IntegrityError:
|
||||
# print("Location %s already exists." % event['location'])
|
||||
pass
|
||||
location, _ = Location.objects.get_or_create(name=event['location'])
|
||||
location.save()
|
||||
|
||||
try:
|
||||
event_obj, new = Event.objects.get_or_create(date=datetime.strptime(date['date'], "%d.%m.%Y"),
|
||||
title=event['title'])
|
||||
if new:
|
||||
event_obj.category = event['category']
|
||||
event_obj.link = event['link']
|
||||
event_obj.time = datetime.strptime(str(event['time']).split()[1], "%H:%M")
|
||||
event_obj.locations.add(Location.objects.get(name=event['location']))
|
||||
event_obj.save()
|
||||
Event.objects.filter(title="").delete()
|
||||
else:
|
||||
print("Event %s already exists. Start Update" % str(event_obj.title))
|
||||
event_obj.category = event['category']
|
||||
event_obj.link = event['link']
|
||||
event_obj.time = datetime.strptime(str(event['time']).split()[1], "%H:%M")
|
||||
event_obj.locations.add(Location.objects.get(name=event['location']))
|
||||
event_obj.save()
|
||||
except IntegrityError:
|
||||
# ignored
|
||||
pass
|
||||
event_obj, _ = Event.objects.get_or_create(date=datetime.strptime(date['date'], "%d.%m.%Y"),
|
||||
title=event['title'])
|
||||
event_obj.category = event['category']
|
||||
event_obj.link = event['link']
|
||||
event_obj.time = datetime.strptime(str(event['time']).split()[1], "%H:%M")
|
||||
event_obj.locations.add(Location.objects.get(name=event['location']))
|
||||
event_obj.save()
|
||||
logger.info('CREATED - Event: {}'.format(event_obj.title))
|
||||
|
||||
|
||||
def deleteUnivisObjects():
|
||||
@ -62,15 +50,13 @@ def writeUnivisLocationsInDB(rooms):
|
||||
for room in rooms:
|
||||
if '@key' in room and 'short' in room:
|
||||
try:
|
||||
Location.objects.create(key=room['@key'], name=room['short'])
|
||||
except IntegrityError:
|
||||
print("Possible Duplicate! Start DB refresh")
|
||||
try:
|
||||
Location.objects.get(name=room['short']).key = room['@key']
|
||||
except Exception as harderr:
|
||||
print("Failed to refresh object" + harderr.args)
|
||||
location, _ = Location.objects.get_or_create(key=room['@key'], name=room['short'])
|
||||
location.key = room['@key']
|
||||
location.name = room['short']
|
||||
location.save()
|
||||
logger.info('CREATE - Location: {}'.format(location.name))
|
||||
except Exception as err:
|
||||
print(err.args)
|
||||
logger.critical(err.args)
|
||||
|
||||
|
||||
def getLocationIDs(event):
|
||||
@ -110,21 +96,24 @@ def writeUnivisEventsInDB(events: list):
|
||||
event_obj.orgname = event['orgname']
|
||||
try:
|
||||
event_obj.save()
|
||||
logger.info(event_obj.title)
|
||||
except IntegrityError:
|
||||
# TODO: Update DB Object if duplicate detected
|
||||
print("Found Duplicate!")
|
||||
logger.info("Found Duplicate!")
|
||||
except Exception as err:
|
||||
print(err.args)
|
||||
logger.exception(err.args)
|
||||
Event.objects.filter(title="").delete()
|
||||
|
||||
|
||||
def write_out_db_objects():
|
||||
pprint("Event: " + str(Event.objects.count()))
|
||||
pprint("Location: " + str(Location.objects.count()))
|
||||
return "\n\tEvent: {event}\n\tLocation: {location}".format(
|
||||
event=Event.objects.count(),
|
||||
location=Location.objects.count(),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
print("Aktueller Stand:")
|
||||
logger.info("Aktueller Stand:")
|
||||
write_out_db_objects()
|
||||
|
||||
# deleteUnivisObjects()
|
||||
@ -139,7 +128,7 @@ def main():
|
||||
|
||||
writeFekideDataInDB(fekide_eventpage_parser.parsePage())
|
||||
|
||||
print("Neuer Stand:")
|
||||
logger.info("Neuer Stand:")
|
||||
write_out_db_objects()
|
||||
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
from apps.food.models import SingleFood, Menu, HappyHour, UserRating, UserFoodImage, FoodImage
|
||||
from apps.food.models import SingleFood, Menu, HappyHour, UserFoodRating, UserFoodImage, FoodImage, UserFoodComment
|
||||
|
||||
|
||||
class SingleFoodInline(admin.TabularInline):
|
||||
@ -22,7 +22,8 @@ class MenuAdmin(admin.ModelAdmin):
|
||||
# Register your models here.
|
||||
admin.site.register(SingleFood)
|
||||
admin.site.register(HappyHour)
|
||||
admin.site.register(UserRating)
|
||||
admin.site.register(UserFoodRating)
|
||||
admin.site.register(UserFoodImage)
|
||||
admin.site.register(UserFoodComment)
|
||||
admin.site.register(Menu, MenuAdmin)
|
||||
admin.site.register(FoodImage)
|
||||
|
||||
0
ofu_app/apps/food/api/v1_1/__init__.py
Normal file
0
ofu_app/apps/food/api/v1_1/__init__.py
Normal file
@ -14,7 +14,7 @@ Including another URLconf
|
||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf.urls import url
|
||||
from apps.food.api import views as api_views
|
||||
from apps.food.api.v1_1 import views as api_views
|
||||
from apps.food.models import Menu
|
||||
|
||||
urlpatterns = [
|
||||
@ -4,11 +4,11 @@ from __future__ import unicode_literals
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from apps.food.api.serializers import MenuSerializer, HappyHourSerializer
|
||||
from apps.food.api.v1_1.serializers import MenuSerializer, HappyHourSerializer
|
||||
from apps.food.models import Menu, HappyHour
|
||||
from rest_framework import viewsets
|
||||
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.decorators import permission_classes
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
||||
|
||||
0
ofu_app/apps/food/api/v1_2/__init__.py
Normal file
0
ofu_app/apps/food/api/v1_2/__init__.py
Normal file
0
ofu_app/apps/food/api/v1_2/serializers/__init__.py
Normal file
0
ofu_app/apps/food/api/v1_2/serializers/__init__.py
Normal file
114
ofu_app/apps/food/api/v1_2/serializers/main_serializers.py
Normal file
114
ofu_app/apps/food/api/v1_2/serializers/main_serializers.py
Normal file
@ -0,0 +1,114 @@
|
||||
from apps.food.models import Menu, SingleFood, HappyHour, Allergene, FoodImage, HappyHourLocation, UserFoodComment
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class DefaultFoodImageSerializer(serializers.Serializer):
|
||||
"""Your data serializer, define your fields here."""
|
||||
|
||||
def create(self, validated_data):
|
||||
pass
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
pass
|
||||
|
||||
image = serializers.CharField()
|
||||
|
||||
|
||||
class MenusLocationsSerializer(serializers.Serializer):
|
||||
"""Your data serializer, define your fields here."""
|
||||
|
||||
def create(self, validated_data):
|
||||
pass
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
pass
|
||||
|
||||
id = serializers.CharField()
|
||||
short = serializers.CharField()
|
||||
name = serializers.CharField()
|
||||
|
||||
|
||||
class UserFoodCommentSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = UserFoodComment
|
||||
fields = ('id', 'description', 'title')
|
||||
|
||||
|
||||
class AllergensSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = Allergene
|
||||
fields = ('id', 'name')
|
||||
|
||||
|
||||
class OverviewFoodImageSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = FoodImage
|
||||
fields = ('id', 'thumb')
|
||||
|
||||
|
||||
class DetailedFoodImageSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = FoodImage
|
||||
fields = ('id', 'image', 'thumb')
|
||||
|
||||
|
||||
class OverviewSingleFoodSerializer(serializers.HyperlinkedModelSerializer):
|
||||
image = OverviewFoodImageSerializer(many=False, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = SingleFood
|
||||
fields = ('id', 'name', 'rating', 'price_student', 'image')
|
||||
|
||||
|
||||
class MinimalSingleFoodSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = SingleFood
|
||||
fields = ('id', 'name')
|
||||
|
||||
|
||||
class DetailedSingleFoosdSerializer(serializers.HyperlinkedModelSerializer):
|
||||
allergens = AllergensSerializer(many=True, read_only=True)
|
||||
image = DetailedFoodImageSerializer(many=False, read_only=True)
|
||||
comments = UserFoodCommentSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = SingleFood
|
||||
fields = (
|
||||
'id', 'name', 'rating', 'price_student', 'price_employee', 'price_guest', 'allergens', 'image', 'comments')
|
||||
|
||||
|
||||
class OverviewMenuSerializer(serializers.HyperlinkedModelSerializer):
|
||||
date = serializers.DateField(format='iso-8601')
|
||||
menu = OverviewSingleFoodSerializer(many=True, read_only=True)
|
||||
location = serializers.ChoiceField(choices=Menu.LOCATION_CHOICES)
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ('id', 'date', 'location', 'menu')
|
||||
|
||||
|
||||
class DetailMenuSerializer(serializers.HyperlinkedModelSerializer):
|
||||
date = serializers.DateField(format='iso-8601')
|
||||
menu = DetailedSingleFoosdSerializer(many=True, read_only=True)
|
||||
location = serializers.ChoiceField(choices=Menu.LOCATION_CHOICES)
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ('id', 'date', 'location', 'menu')
|
||||
|
||||
|
||||
# -------------------------- Happy Hour ------------------------------------
|
||||
class HappyHourSerializer(serializers.HyperlinkedModelSerializer):
|
||||
date = serializers.DateField(format='iso-8601')
|
||||
starttime = serializers.TimeField()
|
||||
endtime = serializers.TimeField()
|
||||
|
||||
class Meta:
|
||||
model = HappyHour
|
||||
fields = ('id', 'date', 'starttime', 'endtime', 'location', 'description')
|
||||
|
||||
|
||||
class HappyHourLocationSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = HappyHourLocation
|
||||
fields = ('id', 'name')
|
||||
100
ofu_app/apps/food/api/v1_2/serializers/user_serializers.py
Normal file
100
ofu_app/apps/food/api/v1_2/serializers/user_serializers.py
Normal file
@ -0,0 +1,100 @@
|
||||
from apps.food.models import Menu, SingleFood
|
||||
from apps.food.models import UserFoodRating, UserFoodImage, UserFoodComment, FoodImage
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework import validators
|
||||
from rest_framework import serializers
|
||||
from django.db.utils import IntegrityError
|
||||
|
||||
|
||||
class UserFoodImageSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = UserFoodImage
|
||||
fields = ('id', 'image_image', 'image_thumb')
|
||||
|
||||
|
||||
class UserFoodCommentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = UserFoodComment
|
||||
fields = ('id', 'title', 'description')
|
||||
|
||||
def run_validators(self, value):
|
||||
for validator in self.validators:
|
||||
if isinstance(validator, validators.UniqueTogetherValidator):
|
||||
self.validators.remove(validator)
|
||||
super(UserFoodCommentSerializer, self).run_validators(value)
|
||||
|
||||
def create(self, validated_data):
|
||||
comment_title = validated_data.pop('title')
|
||||
comment_description = validated_data.pop('description')
|
||||
food_id = self.context.get('food_id')
|
||||
user = self.context['request'].user
|
||||
food = SingleFood.objects.get(id=food_id)
|
||||
|
||||
user_comment, _ = UserFoodComment.objects.get_or_create(food=food, user=user)
|
||||
user_comment.title = comment_title
|
||||
user_comment.description = comment_description
|
||||
user_comment.save()
|
||||
return user_comment
|
||||
|
||||
|
||||
class UserFoodRatingSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = UserFoodRating
|
||||
fields = ('id', 'rating')
|
||||
|
||||
def run_validators(self, value):
|
||||
for validator in self.validators:
|
||||
if isinstance(validator, validators.UniqueTogetherValidator):
|
||||
self.validators.remove(validator)
|
||||
super(UserFoodRatingSerializer, self).run_validators(value)
|
||||
|
||||
def create(self, validated_data):
|
||||
# TODO: Custom exception handler
|
||||
rating = validated_data.pop('rating')
|
||||
if rating >= 1 or rating <= 5:
|
||||
food_id = self.context.get('food_id')
|
||||
user = self.context['request'].user
|
||||
food = SingleFood.objects.get(id=food_id)
|
||||
user_rating, _ = UserFoodRating.objects.get_or_create(food=food, user=user)
|
||||
user_rating.rating = rating
|
||||
user_rating.save()
|
||||
|
||||
food_user_ratings = UserFoodRating.objects.all().filter(food=food)
|
||||
sum = 0
|
||||
for food_user_rating in food_user_ratings:
|
||||
sum += food_user_rating.rating
|
||||
|
||||
food.rating = sum / food_user_ratings.count()
|
||||
food.save()
|
||||
return user_rating
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
|
||||
class UserFoodImageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = FoodImage
|
||||
fields = ('id', 'image')
|
||||
|
||||
def run_validators(self, value):
|
||||
for validator in self.validators:
|
||||
if isinstance(validator, validators.UniqueTogetherValidator):
|
||||
self.validators.remove(validator)
|
||||
super(UserFoodImageSerializer, self).run_validators(value)
|
||||
|
||||
def create(self, validated_data):
|
||||
# TODO: Custom exception handler
|
||||
food_id = self.context.get('food_id')
|
||||
food = SingleFood.objects.get(id=food_id)
|
||||
user = self.context['request'].user
|
||||
image = validated_data.pop('image')
|
||||
food_image = FoodImage.objects.create(image=image)
|
||||
food_image.save()
|
||||
try:
|
||||
user_food_image = UserFoodImage.objects.create(user=user, food=food, image=food_image)
|
||||
user_food_image.save()
|
||||
except IntegrityError as err:
|
||||
user_food_image = UserFoodImage.objects.get(user=user, food=food)
|
||||
user_food_image.image = food_image
|
||||
user_food_image.save()
|
||||
return food_image
|
||||
43
ofu_app/apps/food/api/v1_2/urls.py
Normal file
43
ofu_app/apps/food/api/v1_2/urls.py
Normal file
@ -0,0 +1,43 @@
|
||||
"""ofu_app URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/1.11/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.conf.urls import url, include
|
||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.urls import path
|
||||
from apps.food.api.v1_2.views import main_views as api_views
|
||||
from apps.food.api.v1_2.views import user_views as user_api_views
|
||||
|
||||
urlpatterns = [
|
||||
# API Version 1.2
|
||||
path('food/menus/', api_views.ApiMenus.as_view(), name='menus'),
|
||||
path('food/menus/<int:pk>/', api_views.ApiMenu.as_view(), name='menu'),
|
||||
path('food/menus/locations', api_views.ApiMenusLocations.as_view(), name='menus-locations'),
|
||||
|
||||
path('food/meals/', api_views.ApiMeals.as_view(), name='meals'),
|
||||
path('food/meals/<int:pk>', api_views.ApiMeal.as_view(), name='meal'),
|
||||
path('food/meals/<int:pk>/comments', api_views.ApiMealComments.as_view(), name='meal-comments'),
|
||||
|
||||
path('food/meals/<int:pk>/comment', user_api_views.ApiUserFoodCommentUpload.as_view(), name='meals-comment-upload'),
|
||||
path('food/meals/<int:pk>/rating', user_api_views.ApiFoodRatingUpload.as_view(), name='meals-rating-upload'),
|
||||
path('food/meals/<int:pk>/image', user_api_views.ApiFoodImageUpload.as_view(), name='meals-image-upload'),
|
||||
path('food/meals/image', user_api_views.ApiFoodImageUpload.as_view(), name='meals-image-upload'),
|
||||
|
||||
path('food/meals/images/', api_views.ApiFoodImages.as_view(), name='images'),
|
||||
path('food/meals/images/default', api_views.ApiFoodImagesDefault.as_view(), name='images-default'),
|
||||
|
||||
path('food/allergens/', api_views.ApiAllergens.as_view(), name='allergens'),
|
||||
|
||||
path('food/happy-hours/', api_views.ApiHappyHours.as_view(), name='happy-hours'),
|
||||
path('food/happy-hours/<int:pk>', api_views.ApiHappyHours.as_view(), name='happy-hours'),
|
||||
path('food/happy-hours/locations', api_views.ApiHappyHoursLocations.as_view(), name='happy-hours-locations'),
|
||||
]
|
||||
0
ofu_app/apps/food/api/v1_2/views/__init__.py
Normal file
0
ofu_app/apps/food/api/v1_2/views/__init__.py
Normal file
221
ofu_app/apps/food/api/v1_2/views/main_views.py
Normal file
221
ofu_app/apps/food/api/v1_2/views/main_views.py
Normal file
@ -0,0 +1,221 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.templatetags.static import static
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from apps.food.api.v1_2.serializers.main_serializers import OverviewMenuSerializer, DetailMenuSerializer, \
|
||||
MenusLocationsSerializer
|
||||
from apps.food.api.v1_2.serializers.main_serializers import OverviewSingleFoodSerializer, DetailedSingleFoosdSerializer, \
|
||||
AllergensSerializer, DetailedFoodImageSerializer, DefaultFoodImageSerializer, MinimalSingleFoodSerializer, \
|
||||
UserFoodCommentSerializer
|
||||
from apps.food.api.v1_2.serializers.main_serializers import HappyHourSerializer, HappyHourLocationSerializer
|
||||
from apps.food.models import Menu, SingleFood, Allergene, HappyHour, HappyHourLocation, FoodImage, UserFoodRating, \
|
||||
UserFoodComment
|
||||
from rest_framework import generics
|
||||
from rest_framework.decorators import permission_classes, api_view, authentication_classes
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework import views, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiMenus(generics.ListAPIView):
|
||||
serializer_class = OverviewMenuSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = Menu.objects.all()
|
||||
location = self.request.query_params.get('location')
|
||||
start_date = self.request.query_params.get('startdate')
|
||||
end_date = self.request.query_params.get('enddate')
|
||||
|
||||
if location:
|
||||
if str(location).upper() == Menu.ERBA.upper():
|
||||
queryset = queryset.filter(location__contains=Menu.ERBA)
|
||||
elif str(location).upper() == Menu.FEKI.upper():
|
||||
queryset = queryset.filter(location__contains=Menu.FEKI)
|
||||
elif str(location).upper() == Menu.AUSTRASSE.upper():
|
||||
queryset = queryset.filter(location__contains=Menu.AUSTRASSE)
|
||||
elif str(location).upper() == Menu.MARKUSPLATZ.upper():
|
||||
queryset = queryset.filter(location__contains=Menu.MARKUSPLATZ)
|
||||
else:
|
||||
queryset = []
|
||||
if start_date:
|
||||
try:
|
||||
queryset = queryset.filter(date__gte=datetime.strptime(start_date, '%Y-%m-%d'))
|
||||
except ValueError as e:
|
||||
# TODO: return Exception
|
||||
return []
|
||||
|
||||
if end_date:
|
||||
try:
|
||||
queryset = queryset.filter(date__lte=datetime.strptime(end_date, '%Y-%m-%d'))
|
||||
except ValueError as e:
|
||||
# TODO: return Exception
|
||||
return []
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiMenu(generics.RetrieveAPIView):
|
||||
serializer_class = DetailMenuSerializer
|
||||
queryset = Menu.objects.all()
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiMeals(generics.ListAPIView):
|
||||
serializer_class = OverviewSingleFoodSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = SingleFood.objects.all()
|
||||
rating = self.request.query_params.get('rating')
|
||||
max_rating = self.request.query_params.get('max-rating')
|
||||
min_rating = self.request.query_params.get('min-rating')
|
||||
|
||||
price = self.request.query_params.get('price')
|
||||
max_price = self.request.query_params.get('max-price')
|
||||
min_price = self.request.query_params.get('min-price')
|
||||
|
||||
allergens = self.request.query_params.get('allergens')
|
||||
|
||||
if rating:
|
||||
queryset = queryset.filter(rating=rating)
|
||||
|
||||
if max_rating:
|
||||
queryset = queryset.filter(rating__lte=max_rating)
|
||||
|
||||
if min_rating:
|
||||
queryset = queryset.filter(rating__gte=min_rating)
|
||||
|
||||
# TODO: Change price model to Floatfield
|
||||
# if price:
|
||||
# pass
|
||||
#
|
||||
# if max_price:
|
||||
# pass
|
||||
#
|
||||
# if min_price:
|
||||
# pass
|
||||
|
||||
if allergens:
|
||||
allergens = [allergen for allergen in str(allergens).strip('[]').split(',')]
|
||||
queryset = queryset.filter(allergens__id__in=allergens)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiMeal(generics.RetrieveAPIView):
|
||||
serializer_class = DetailedSingleFoosdSerializer
|
||||
queryset = SingleFood.objects.all()
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiAllergens(generics.ListAPIView):
|
||||
serializer_class = AllergensSerializer
|
||||
queryset = Allergene.objects.all()
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiMenusLocations(views.APIView):
|
||||
|
||||
def get(self, request):
|
||||
locations = Menu.API_LOCATIONS
|
||||
results = MenusLocationsSerializer(locations, many=True).data
|
||||
return Response(results, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiMealComments(generics.ListAPIView):
|
||||
serializer_class = UserFoodCommentSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
food_id = self.kwargs['pk']
|
||||
return UserFoodComment.objects.filter(food_id=food_id)
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiHappyHours(generics.ListAPIView):
|
||||
serializer_class = HappyHourSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = HappyHour.objects.all()
|
||||
date = self.request.query_params.get('date')
|
||||
start_date = self.request.query_params.get('startdate')
|
||||
end_date = self.request.query_params.get('enddate')
|
||||
|
||||
start_time = self.request.query_params.get('starttime')
|
||||
end_time = self.request.query_params.get('endtime')
|
||||
|
||||
location = self.request.query_params.get('location')
|
||||
if date:
|
||||
try:
|
||||
queryset = queryset.filter(date=datetime.strptime(date, '%Y-%m-%d'))
|
||||
except ValueError as e:
|
||||
# TODO: return Exception
|
||||
return []
|
||||
|
||||
if start_date:
|
||||
try:
|
||||
queryset = queryset.filter(date__gte=datetime.strptime(start_date, '%Y-%m-%d'))
|
||||
except ValueError as e:
|
||||
# TODO: return Exception
|
||||
return []
|
||||
|
||||
if end_date:
|
||||
try:
|
||||
queryset = queryset.filter(date__lte=datetime.strptime(start_date, '%Y-%m-%d'))
|
||||
except ValueError as e:
|
||||
# TODO: return Exception
|
||||
return []
|
||||
|
||||
if start_time:
|
||||
try:
|
||||
queryset = queryset.filter(date__lte=datetime.strptime(start_time, '%H'))
|
||||
except ValueError as e:
|
||||
# TODO: return Exception
|
||||
return []
|
||||
|
||||
if end_time:
|
||||
try:
|
||||
queryset = queryset.filter(date__lte=datetime.strptime(end_time, '%H'))
|
||||
except ValueError as e:
|
||||
# TODO: return Exception
|
||||
return []
|
||||
|
||||
if location:
|
||||
queryset = queryset.filter(location__id=location)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiHappyHoursLocations(generics.RetrieveAPIView):
|
||||
serializer_class = HappyHourSerializer
|
||||
queryset = HappyHour.objects.all()
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiHappyHoursLocations(generics.ListAPIView):
|
||||
serializer_class = HappyHourLocationSerializer
|
||||
queryset = HappyHourLocation.objects.all()
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiFoodImages(generics.ListAPIView):
|
||||
serializer_class = DetailedFoodImageSerializer
|
||||
queryset = FoodImage.objects.all()
|
||||
|
||||
|
||||
@permission_classes((AllowAny,))
|
||||
class ApiFoodImagesDefault(views.APIView):
|
||||
|
||||
def get(self, request):
|
||||
request.build_absolute_uri(static('img/food/default.jpg'))
|
||||
default_image = {'image': request.build_absolute_uri(static('img/food/default.jpg'))}
|
||||
results = DefaultFoodImageSerializer(default_image, many=False).data
|
||||
return Response(results, status=status.HTTP_200_OK)
|
||||
59
ofu_app/apps/food/api/v1_2/views/user_views.py
Normal file
59
ofu_app/apps/food/api/v1_2/views/user_views.py
Normal file
@ -0,0 +1,59 @@
|
||||
from rest_framework import generics
|
||||
from rest_framework.decorators import permission_classes, api_view, authentication_classes
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework.authentication import TokenAuthentication, SessionAuthentication
|
||||
from rest_framework.parsers import FormParser, MultiPartParser
|
||||
|
||||
from apps.food.models import UserFoodRating, UserFoodImage, UserFoodComment, FoodImage
|
||||
from apps.food.api.v1_2.serializers.user_serializers import UserFoodRatingSerializer, UserFoodImageSerializer, \
|
||||
UserFoodCommentSerializer
|
||||
|
||||
|
||||
@authentication_classes((TokenAuthentication, SessionAuthentication))
|
||||
@permission_classes((IsAuthenticated,))
|
||||
class ApiFoodRatingUpload(generics.CreateAPIView):
|
||||
serializer_class = UserFoodRatingSerializer
|
||||
queryset = UserFoodRating.objects.all()
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super(ApiFoodRatingUpload, self).get_serializer_context()
|
||||
context.update({
|
||||
"food_id": self.kwargs['pk'],
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
@authentication_classes((TokenAuthentication, SessionAuthentication))
|
||||
@permission_classes((IsAuthenticated,))
|
||||
class ApiUserFoodCommentUpload(generics.CreateAPIView):
|
||||
serializer_class = UserFoodCommentSerializer
|
||||
queryset = UserFoodComment.objects.all()
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super(ApiUserFoodCommentUpload, self).get_serializer_context()
|
||||
context.update({
|
||||
"food_id": self.kwargs['pk'],
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
@authentication_classes((TokenAuthentication, SessionAuthentication))
|
||||
@permission_classes((IsAuthenticated,))
|
||||
class ApiFoodImageUpload(generics.CreateAPIView):
|
||||
serializer_class = UserFoodImageSerializer
|
||||
queryset = FoodImage.objects.all()
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super(ApiFoodImageUpload, self).get_serializer_context()
|
||||
context.update({
|
||||
"food_id": self.kwargs['pk'],
|
||||
})
|
||||
return context
|
||||
#
|
||||
# @authentication_classes((TokenAuthentication,))
|
||||
# @permission_classes((IsAuthenticated,))
|
||||
# class ApiFoodImageUpload(generics.CreateAPIView):
|
||||
# serializer_class = UserFoodImageSerializer
|
||||
# queryset = UserFoodImage.objects.all()
|
||||
#
|
||||
#
|
||||
@ -1,5 +1,5 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from apps.food.models import Menu, HappyHour, SingleFood
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from apps.food.utils import migrate_data
|
||||
|
||||
|
||||
|
||||
49
ofu_app/apps/food/migrations/0009_auto_20180324_0039.py
Normal file
49
ofu_app/apps/food/migrations/0009_auto_20180324_0039.py
Normal file
@ -0,0 +1,49 @@
|
||||
# Generated by Django 2.0.1 on 2018-03-24 00:39
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('food', '0008_auto_20180201_1018'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UserFoodComment',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('comment', models.CharField(max_length=2048)),
|
||||
('food', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='food.SingleFood')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserFoodRating',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('rating', models.FloatField(default=0)),
|
||||
('food', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='food.SingleFood')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='userrating',
|
||||
name='food',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='userrating',
|
||||
name='user',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='UserRating',
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='userfoodcomment',
|
||||
unique_together={('user', 'food')},
|
||||
),
|
||||
]
|
||||
26
ofu_app/apps/food/migrations/0010_auto_20180325_1630.py
Normal file
26
ofu_app/apps/food/migrations/0010_auto_20180325_1630.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Generated by Django 2.0.1 on 2018-03-25 16:30
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('food', '0009_auto_20180324_0039'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HappyHourLocation',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=256, unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='happyhour',
|
||||
name='location',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='food.HappyHourLocation'),
|
||||
),
|
||||
]
|
||||
23
ofu_app/apps/food/migrations/0011_auto_20180326_2255.py
Normal file
23
ofu_app/apps/food/migrations/0011_auto_20180326_2255.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 2.0.1 on 2018-03-26 22:55
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('food', '0010_auto_20180325_1630'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name='happyhour',
|
||||
unique_together={('location', 'starttime', 'endtime')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='userfoodrating',
|
||||
unique_together={('user', 'food')},
|
||||
),
|
||||
]
|
||||
19
ofu_app/apps/food/migrations/0012_auto_20180326_2343.py
Normal file
19
ofu_app/apps/food/migrations/0012_auto_20180326_2343.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.0.1 on 2018-03-26 23:43
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('food', '0011_auto_20180326_2255'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userfoodrating',
|
||||
name='rating',
|
||||
field=models.FloatField(default=0, validators=[django.core.validators.MaxValueValidator(5), django.core.validators.MinValueValidator(0)]),
|
||||
),
|
||||
]
|
||||
24
ofu_app/apps/food/migrations/0013_auto_20180330_1045.py
Normal file
24
ofu_app/apps/food/migrations/0013_auto_20180330_1045.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Generated by Django 2.0.1 on 2018-03-30 10:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('food', '0012_auto_20180326_2343'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='userfoodcomment',
|
||||
old_name='comment',
|
||||
new_name='description',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='userfoodcomment',
|
||||
name='title',
|
||||
field=models.CharField(default='Test', max_length=128),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
18
ofu_app/apps/food/migrations/0014_singlefood_comments.py
Normal file
18
ofu_app/apps/food/migrations/0014_singlefood_comments.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.0.1 on 2018-03-30 11:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('food', '0013_auto_20180330_1045'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='singlefood',
|
||||
name='comments',
|
||||
field=models.ManyToManyField(blank=True, to='food.UserFoodComment'),
|
||||
),
|
||||
]
|
||||
23
ofu_app/apps/food/migrations/0015_auto_20180331_1427.py
Normal file
23
ofu_app/apps/food/migrations/0015_auto_20180331_1427.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 2.0.1 on 2018-03-31 14:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('food', '0014_singlefood_comments'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='foodimage',
|
||||
name='thumb',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='foodimage',
|
||||
name='image',
|
||||
field=models.ImageField(default='NULL', upload_to='food/originals/%Y/%m/%W'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
25
ofu_app/apps/food/migrations/0016_auto_20180331_1446.py
Normal file
25
ofu_app/apps/food/migrations/0016_auto_20180331_1446.py
Normal file
@ -0,0 +1,25 @@
|
||||
# Generated by Django 2.0.1 on 2018-03-31 14:46
|
||||
|
||||
import apps.food.models
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('food', '0015_auto_20180331_1427'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='foodimage',
|
||||
name='image',
|
||||
field=models.ImageField(upload_to=apps.food.models.image_path),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='singlefood',
|
||||
name='image',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='food.FoodImage'),
|
||||
),
|
||||
]
|
||||
18
ofu_app/apps/food/migrations/0017_foodimage_thumb.py
Normal file
18
ofu_app/apps/food/migrations/0017_foodimage_thumb.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.0.1 on 2018-03-31 15:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('food', '0016_auto_20180331_1446'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='foodimage',
|
||||
name='thumb',
|
||||
field=models.ImageField(blank=True, null=True, upload_to='food/thumbs/%Y/%m/%W'),
|
||||
),
|
||||
]
|
||||
17
ofu_app/apps/food/migrations/0018_remove_foodimage_thumb.py
Normal file
17
ofu_app/apps/food/migrations/0018_remove_foodimage_thumb.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 2.0.1 on 2018-03-31 15:53
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('food', '0017_foodimage_thumb'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='foodimage',
|
||||
name='thumb',
|
||||
),
|
||||
]
|
||||
19
ofu_app/apps/food/migrations/0019_foodimage_thumb.py
Normal file
19
ofu_app/apps/food/migrations/0019_foodimage_thumb.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.0.1 on 2018-03-31 16:09
|
||||
|
||||
import apps.food.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('food', '0018_remove_foodimage_thumb'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='foodimage',
|
||||
name='thumb',
|
||||
field=models.ImageField(blank=True, null=True, upload_to=apps.food.models.thumb_path),
|
||||
),
|
||||
]
|
||||
@ -3,6 +3,8 @@ from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
from io import BytesIO
|
||||
import uuid
|
||||
from _datetime import datetime
|
||||
|
||||
from PIL import Image
|
||||
from django.conf import settings
|
||||
@ -10,6 +12,7 @@ from django.contrib.auth.models import User
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
|
||||
MAX_LENGTH = 256
|
||||
MAX_FOOD_NAME = 256
|
||||
@ -18,6 +21,20 @@ MAX_FOOD_PRICE_LENGTH = 10
|
||||
MAX_FOOD_ALLERGENNAME_LENGTH = 256
|
||||
MAX_HAPPY_HOUR_LOCATION_LENGTH = 256
|
||||
MAX_HAPPY_HOUR_DESCRIPTION_LENGTH = 1024
|
||||
MAX_FOOD_COMMENT_TITLE_LENGTH = 128
|
||||
MAX_FOOD_COMMENT_LENGTH = 2048
|
||||
|
||||
|
||||
def image_path(instance, filename):
|
||||
extension = filename.split('.')[-1]
|
||||
date = datetime.now().strftime('%Y/%m/%W')
|
||||
return 'food/originals/{}/{}.{}'.format(date, uuid.uuid4(), extension)
|
||||
|
||||
|
||||
def thumb_path(instance, filename):
|
||||
extension = filename.split('.')[-1]
|
||||
date = datetime.now().strftime('%Y/%m/%W')
|
||||
return 'food/thumbs/{}/{}.{}'.format(date, uuid.uuid4(), 'jpg')
|
||||
|
||||
|
||||
# Create your models here.
|
||||
@ -29,6 +46,13 @@ class Menu(models.Model):
|
||||
|
||||
LOCATION_CHOICES = (
|
||||
(ERBA, 'Erba'), (MARKUSPLATZ, 'Markusplatz'), (FEKI, 'Feldkirchenstrasse'), (AUSTRASSE, 'Austrasse'))
|
||||
|
||||
# Api location data
|
||||
API_LOCATIONS = [{'id': FEKI, 'name': 'Feldkirchenstrasse', 'short': 'Feki'},
|
||||
{'id': AUSTRASSE, 'name': 'Austrasse', 'short': 'Austrasse'},
|
||||
{'id': ERBA, 'name': 'Erba', 'short': 'Erba'},
|
||||
{'id': MARKUSPLATZ, 'name': 'Markusplatz', 'short': 'Markusplatz'}, ]
|
||||
|
||||
id = models.AutoField(primary_key=True)
|
||||
date = models.DateField(default=timezone.now)
|
||||
location = models.CharField(max_length=MAX_FOOD_LOCATION_LENGTH, choices=LOCATION_CHOICES)
|
||||
@ -47,9 +71,10 @@ class SingleFood(models.Model):
|
||||
price_student = models.CharField(max_length=MAX_FOOD_PRICE_LENGTH, blank=True, null=True)
|
||||
price_employee = models.CharField(max_length=MAX_FOOD_PRICE_LENGTH, blank=True, null=True)
|
||||
price_guest = models.CharField(max_length=MAX_FOOD_PRICE_LENGTH, blank=True, null=True)
|
||||
image = models.ForeignKey('FoodImage', on_delete=models.PROTECT, blank=True, null=True)
|
||||
image = models.ForeignKey('FoodImage', on_delete=models.SET_NULL, blank=True, null=True)
|
||||
rating = models.FloatField(default=0)
|
||||
allergens = models.ManyToManyField("Allergene", blank=True)
|
||||
comments = models.ManyToManyField('UserFoodComment', blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return "%s - Rating: %f - Student Price: %s" % (self.name, self.rating, self.price_student)
|
||||
@ -68,22 +93,32 @@ class HappyHour(models.Model):
|
||||
date = models.DateField(default=timezone.now)
|
||||
starttime = models.TimeField(default=timezone.now)
|
||||
endtime = models.TimeField(default=timezone.now)
|
||||
location = models.CharField(max_length=MAX_HAPPY_HOUR_LOCATION_LENGTH)
|
||||
location = models.ForeignKey('HappyHourLocation', on_delete=models.PROTECT)
|
||||
description = models.CharField(max_length=MAX_HAPPY_HOUR_DESCRIPTION_LENGTH)
|
||||
|
||||
class Meta:
|
||||
# TODO: unique description instead of date
|
||||
unique_together = ('date', 'location', 'starttime', 'endtime')
|
||||
unique_together = ('location', 'starttime', 'endtime')
|
||||
|
||||
def __str__(self):
|
||||
return "Date: %s, Location: %s" % (self.date.strftime("%Y.%m.%d"), self.location)
|
||||
|
||||
|
||||
class UserRating(models.Model):
|
||||
class HappyHourLocation(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(unique=True, max_length=MAX_HAPPY_HOUR_LOCATION_LENGTH)
|
||||
|
||||
def __str__(self):
|
||||
return "%s" % self.name
|
||||
|
||||
|
||||
class UserFoodRating(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
user = models.ForeignKey(User, on_delete=models.PROTECT, unique=False)
|
||||
food = models.ForeignKey(SingleFood, on_delete=models.PROTECT)
|
||||
rating = models.FloatField(default=0)
|
||||
rating = models.FloatField(default=0, validators=[MaxValueValidator(5), MinValueValidator(0)])
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'food')
|
||||
|
||||
def __str__(self):
|
||||
return "User: %s - Rating: %s" % (self.user.username, self.rating)
|
||||
@ -102,16 +137,31 @@ class UserFoodImage(models.Model):
|
||||
return "User: %s - Image: %s" % (self.user.username, str(self.image))
|
||||
|
||||
|
||||
class UserFoodComment(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
user = models.ForeignKey(User, on_delete=models.PROTECT, unique=False)
|
||||
food = models.ForeignKey(SingleFood, on_delete=models.PROTECT)
|
||||
title = models.CharField(max_length=MAX_FOOD_COMMENT_TITLE_LENGTH, null=False, blank=False)
|
||||
description = models.CharField(max_length=MAX_FOOD_COMMENT_LENGTH, null=False, blank=False)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'food')
|
||||
|
||||
def __str__(self):
|
||||
return "User: %s - Title: %s" % (self.user.username, self.title)
|
||||
|
||||
|
||||
class FoodImage(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
image = models.ImageField(upload_to='food/originals/%Y/%m/%W', blank=True, null=True)
|
||||
thumb = models.ImageField(upload_to='food/thumbs/%Y/%m/%W', blank=True, null=True)
|
||||
image = models.ImageField(upload_to=image_path, blank=False, null=False)
|
||||
thumb = models.ImageField(upload_to=thumb_path, blank=True, null=True)
|
||||
|
||||
def save(self, force_update=False, force_insert=False, thumb_size=(640, 480)):
|
||||
def save(self, *args, **kwargs):
|
||||
image = Image.open(self.image)
|
||||
|
||||
if image.mode not in ('L', 'RGB'):
|
||||
image = image.convert('RGB')
|
||||
thumb_size = (128, 128)
|
||||
image.thumbnail(thumb_size, Image.ANTIALIAS)
|
||||
|
||||
# save the thumbnail to memory
|
||||
@ -124,12 +174,17 @@ class FoodImage(models.Model):
|
||||
temp_handle.read(),
|
||||
content_type='image/jpg')
|
||||
|
||||
self.thumb.save('%s_thumbnail.%s' % (self.id, 'jpg'), suf, save=False)
|
||||
# save the image object
|
||||
self.image.name = "%s_original.%s" % (self.id, 'jpg')
|
||||
super(FoodImage, self).save(force_update, force_insert)
|
||||
# self.thumb.save('%s_thumbnail.%s' % (self.id, 'jpg'), suf, save=False)
|
||||
self.thumb.save(name='', content=suf, save=False)
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
os.remove(os.path.join(settings.MEDIA_ROOT, self.image.name))
|
||||
os.remove(os.path.join(settings.MEDIA_ROOT, self.thumb.name))
|
||||
super(FoodImage, self).delete()
|
||||
# save the image object
|
||||
super(FoodImage, self).save(*args, **kwargs)
|
||||
|
||||
#
|
||||
# def delete(self, using=None, keep_parents=False):
|
||||
# os.remove(os.path.join(settings.MEDIA_ROOT, self.image.name))
|
||||
# os.remove(os.path.join(settings.MEDIA_ROOT, self.thumb.name))
|
||||
# super(FoodImage, self).delete()
|
||||
|
||||
def __str__(self):
|
||||
return "Image: %s" % (str(self.image))
|
||||
|
||||
0
ofu_app/apps/food/templates/__init__.py
Normal file
0
ofu_app/apps/food/templates/__init__.py
Normal file
126
ofu_app/apps/food/templates/root_api.jinja
Normal file
126
ofu_app/apps/food/templates/root_api.jinja
Normal file
@ -0,0 +1,126 @@
|
||||
{% import '/macros/nav.jinja' as nav %}
|
||||
{# ===== HTML ===== #}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
{# ===== Head ===== #}
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>BaStA</title>
|
||||
<meta name="author" content="Michael Götz"/>
|
||||
<script src="{{ static('libs/jquery/jquery-3.2.1.min.js') }}"></script>
|
||||
<script src="{{ static('libs/bootstrap-4.0.0-beta-dist/js/bootstrap.js') }}"></script>
|
||||
{% block js_extra %}{% endblock %}
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="{{ static('libs/font-awesome-4.7.0/css/font-awesome.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('libs/bootstrap-4.0.0-beta-dist/css/bootstrap.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('css/nav.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('css/main.css') }}">
|
||||
{% block css_extra %}{% endblock %}
|
||||
<!-- Piwik -->
|
||||
<script type="text/javascript">
|
||||
var _paq = _paq || [];
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function () {
|
||||
var u = "//mg-server.ddns.net/piwik/";
|
||||
_paq.push(['setTrackerUrl', u + 'piwik.php']);
|
||||
_paq.push(['setSiteId', '1']);
|
||||
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
|
||||
g.type = 'text/javascript';
|
||||
g.async = true;
|
||||
g.defer = true;
|
||||
g.src = u + 'piwik.js';
|
||||
s.parentNode.insertBefore(g, s);
|
||||
})();
|
||||
</script>
|
||||
<!-- End Piwik Code -->
|
||||
</head>
|
||||
|
||||
{# ===== Body ===== #}
|
||||
<body>
|
||||
{% block body %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-2 text-center m-auto">
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenuButton"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-user" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
||||
<a class="dropdown-item" href="{{ url('account') }}"><i class="fa fa-user"
|
||||
aria-hidden="true"></i> Profile</a>
|
||||
<a class="dropdown-item" href="{{ url('logout') }}"><i class="fa fa-sign-out"
|
||||
aria-hidden="true"></i> Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="{{ url('login') }}"><i class="fa fa-sign-in" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-8 pt-2">
|
||||
{% block headline %}{% endblock %}</div>
|
||||
<div class="col-2 text-center m-auto">
|
||||
<div id="menu-button"><i class="fa fa-bars" aria-hidden="true"></i>
|
||||
{{ nav.main_nav() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">{% block bottom_nav %}{% endblock %}</div>
|
||||
<div class="test row bg-dark text-white">
|
||||
{% block content %}{% endblock %}
|
||||
<div class="col">
|
||||
<table class="table">
|
||||
<tr><th>Method</th><th>Request URL</th><th>Description</th></tr>
|
||||
<tr><td></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block test %}
|
||||
<div class="row text-center bg-warning pb-2 pl-3 pr-3" style="font-size: 12px !important;">
|
||||
<div class="col-12 text-center">
|
||||
Hinweis: Diese Seite dient <strong>nur</strong> zu Testzwecken.
|
||||
Wir garantieren weder die Vollständigkeit, noch
|
||||
die Korrektheit der dargestellten Daten.
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
<footer>
|
||||
<div class="row bg-dark text-white">
|
||||
<div class="col-6">
|
||||
<p class="text-right mb-0"><a href="{{ url('impressum') }}#bug-report">Bug Report</a></p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="text-left mb-0"><a href="{{ url('impressum') }}">Impressum</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-center bg-dark text-white pb-2">
|
||||
<div class="col">
|
||||
© Copyright 2018, Michael Götz
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
<!-- Optional JavaScript -->
|
||||
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
|
||||
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js"
|
||||
integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js"
|
||||
integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1"
|
||||
crossorigin="anonymous"></script>
|
||||
{% block js_tail %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@ -4,9 +4,8 @@ from __future__ import unicode_literals
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from apps.food.models import SingleFood, Menu
|
||||
from apps.food.api.serializers import MenuSerializer
|
||||
from apps.food.api.v1_1.serializers import MenuSerializer
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIRequestFactory
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@ -13,12 +13,11 @@ Including another URLconf
|
||||
1. Import the include() function: from django.conf.urls import url, include
|
||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf.urls import url, include
|
||||
from rest_framework import routers
|
||||
|
||||
from apps.food import views, admin_views
|
||||
from django.conf.urls import url, include
|
||||
from apps.food.api import views as api_views
|
||||
from django.conf.urls import url
|
||||
from apps.food.api.v1_1 import views as api_views
|
||||
|
||||
# API Version 1.0
|
||||
apiRouter_v1 = routers.DefaultRouter()
|
||||
|
||||
@ -2,8 +2,11 @@ import json
|
||||
from datetime import datetime
|
||||
from pprint import pprint
|
||||
from django.db.utils import IntegrityError
|
||||
from apps.food.models import SingleFood, Menu, HappyHour, Allergene
|
||||
from apps.food.models import SingleFood, Menu, HappyHour, Allergene, HappyHourLocation
|
||||
from apps.food.utils.parser import mensa_page_parser, fekide_happyhour_page_parser, cafete_page_parser
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# CONFIG SERVICE LINKS
|
||||
LINK_FEKI_MENSA = "https://www.studentenwerk-wuerzburg.de/bamberg/essen-trinken/speiseplaene.html?tx_thmensamenu_pi2%5Bmensen%5D=3&tx_thmensamenu_pi2%5Baction%5D=show&tx_thmensamenu_pi2%5Bcontroller%5D=Speiseplan&cHash=c3fe5ebb35e5fba3794f01878e798b7c"
|
||||
@ -15,128 +18,112 @@ LINK_FEKIDE_GUIDE = "https://www.feki.de/happyhour"
|
||||
LOCATION_NAMES = ('erba', 'markusplatz', 'feldkirchenstraße', 'austraße')
|
||||
|
||||
|
||||
def getJsonFromFile(path):
|
||||
with open(path, "r") as file:
|
||||
return json.load(file)
|
||||
|
||||
|
||||
def getLocation(raw_loc):
|
||||
for choice, name in zip(Menu.LOCATION_CHOICES, LOCATION_NAMES):
|
||||
print(name.upper() in str(raw_loc).upper())
|
||||
if (name.upper() in str(raw_loc).upper()):
|
||||
if name.upper() in str(raw_loc).upper():
|
||||
return choice
|
||||
|
||||
print("LOCATION NOT FOUND")
|
||||
logger.warning("{loc} unknown location".format(loc=raw_loc))
|
||||
return None
|
||||
|
||||
|
||||
def writeStudentenwerkDataInDB(data):
|
||||
data = json.loads(data)
|
||||
pprint(data)
|
||||
if not data:
|
||||
logger.warning('no data')
|
||||
return
|
||||
logger.info("{location}".format(location=data['name']))
|
||||
for menu in data['weekmenu']:
|
||||
pprint(menu)
|
||||
logger.info("{date}".format(date=menu['date']))
|
||||
foodlist = []
|
||||
for single_food in menu['menu']:
|
||||
pprint(single_food)
|
||||
logger.info("{}".format(single_food['title']))
|
||||
allergens = []
|
||||
if 'allergens' in single_food:
|
||||
allergens = []
|
||||
for allergen in single_food['allergens']:
|
||||
try:
|
||||
allergens.append(Allergene.objects.create(name=allergen))
|
||||
except IntegrityError:
|
||||
allergens.append(Allergene.objects.get(name=allergen))
|
||||
allergens.append(Allergene.objects.get_or_create(name=allergen)[0])
|
||||
# TODO: Consider keyword arg for price
|
||||
try:
|
||||
if 'prices' in single_food:
|
||||
if 'price_student' in single_food['prices']:
|
||||
price_student = single_food['prices']['price_student']
|
||||
else:
|
||||
price_student = "None"
|
||||
if 'price_employee' in single_food['prices']:
|
||||
price_employee = single_food['prices']['price_employee']
|
||||
else:
|
||||
price_employee = "None"
|
||||
if 'price_guest' in single_food['prices']:
|
||||
price_guest = single_food['prices']['price_guest']
|
||||
else:
|
||||
price_guest = "None"
|
||||
db_single_food = SingleFood.objects.create(name=single_food['title'],
|
||||
price_student=price_student,
|
||||
price_employee=price_employee,
|
||||
price_guest=price_guest)
|
||||
else:
|
||||
db_single_food = SingleFood.objects.create(name=single_food['title'])
|
||||
if 'allergens' in locals():
|
||||
db_single_food.allergens.set(allergens)
|
||||
foodlist.append(db_single_food)
|
||||
except IntegrityError:
|
||||
db_single_food = SingleFood.objects.get(name=single_food['title'])
|
||||
db_single_food, created = SingleFood.objects.get_or_create(name=single_food['title'])
|
||||
if 'prices' in single_food:
|
||||
if 'price_student' in single_food['prices']:
|
||||
db_single_food.price_student = single_food['prices']['price_student']
|
||||
else:
|
||||
db_single_food.price_student = "None"
|
||||
if 'price_employee' in single_food['prices']:
|
||||
db_single_food.price_employee = single_food['prices']['price_employee']
|
||||
else:
|
||||
db_single_food.price_employee = "None"
|
||||
if 'price_guest' in single_food['prices']:
|
||||
db_single_food.price_guest = single_food['prices']['price_guest']
|
||||
if 'allergens' in locals():
|
||||
else:
|
||||
db_single_food.price_guest = "None"
|
||||
if allergens:
|
||||
db_single_food.allergens.set(allergens)
|
||||
foodlist.append(db_single_food)
|
||||
try:
|
||||
db_single_food.save()
|
||||
except IntegrityError as e:
|
||||
logger.exception(e)
|
||||
|
||||
try:
|
||||
date = datetime.strptime(str(menu['date']), "%d.%m.").replace(year=datetime.today().year)
|
||||
menu = Menu.objects.create(location=getLocation(data['name']), date=date)
|
||||
menu, _ = Menu.objects.get_or_create(location=getLocation(data['name']), date=date)
|
||||
menu.menu.set(foodlist)
|
||||
menu.save()
|
||||
except IntegrityError as error:
|
||||
# ignored
|
||||
pass
|
||||
logger.exception(error)
|
||||
|
||||
|
||||
def writeFekideDataInDB(data):
|
||||
for happyhour_data in data['happyhours']:
|
||||
time = str(happyhour_data['time']).replace(" ", "").split("-")
|
||||
happyhour, new = HappyHour.objects.get_or_create(date=datetime.strptime(data['day'], "%A, %d.%m.%Y"),
|
||||
location=happyhour_data['location'],
|
||||
description=happyhour_data['description'],
|
||||
starttime=datetime.strptime(time[0], "%H:%M").time(),
|
||||
endtime=datetime.strptime(time[1], "%H:%M").time())
|
||||
if not new:
|
||||
try:
|
||||
location, _ = HappyHourLocation.objects.get_or_create(name=happyhour_data['location'])
|
||||
happyhour, _ = HappyHour.objects.get_or_create(location=location,
|
||||
starttime=datetime.strptime(time[0], "%H:%M").time(),
|
||||
endtime=datetime.strptime(time[1], "%H:%M").time())
|
||||
happyhour.date = datetime.strptime(data['day'], "%A, %d.%m.%Y")
|
||||
happyhour.location = happyhour_data['location']
|
||||
happyhour.description = happyhour_data['description']
|
||||
happyhour.starttime = datetime.strptime(time[0], "%H:%M").time()
|
||||
happyhour.endtime = datetime.strptime(time[1], "%H:%M").time()
|
||||
happyhour.save()
|
||||
|
||||
print("%s: Happy Hour: Location: %s, Description: %s" % (
|
||||
str(happyhour.date.date()), str(happyhour.location), str(happyhour.description)))
|
||||
logger.info("{date}: Happy Hour: Location: {location}, Description: {description}".format(
|
||||
date=happyhour.date,
|
||||
location=happyhour.location,
|
||||
description=happyhour.description)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
|
||||
def writeoutDBObjects():
|
||||
pprint("SingleFood: " + str(SingleFood.objects.count()))
|
||||
pprint("Menu: " + str(Menu.objects.count()))
|
||||
pprint("HappyHour: " + str(HappyHour.objects.count()))
|
||||
return "\n\tSingleFood: {single_food}\n\tMenu: {menu}\n\tHappyHour: {happy_hour}".format(
|
||||
single_food=SingleFood.objects.count(),
|
||||
menu=Menu.objects.count(),
|
||||
happy_hour=HappyHour.objects.count()
|
||||
)
|
||||
|
||||
|
||||
def delete():
|
||||
happy_hours = HappyHour.objects.all()
|
||||
print("Deleted following Happy Hours:")
|
||||
logger.info("Deleted following Happy Hours:")
|
||||
for happy_hour in happy_hours:
|
||||
print("%s: Happy Hour: Location: %s, Description: %s" % (
|
||||
str(happy_hour.date), str(happy_hour.location), str(happy_hour.description)))
|
||||
logger.info("{date}: Happy Hour: Location: {location}, Description: {description}".format(
|
||||
date=happy_hour.date,
|
||||
location=happy_hour.location,
|
||||
description=happy_hour.description)
|
||||
)
|
||||
happy_hour.delete()
|
||||
|
||||
|
||||
def main():
|
||||
print("Aktueller Stand:")
|
||||
writeoutDBObjects()
|
||||
logger.info("Aktueller Stand:" + writeoutDBObjects())
|
||||
|
||||
# get food jsons
|
||||
writeStudentenwerkDataInDB(mensa_page_parser.parsePage(LINK_AUSTR_MENSA))
|
||||
writeStudentenwerkDataInDB(mensa_page_parser.parsePage(LINK_FEKI_MENSA))
|
||||
writeStudentenwerkDataInDB(cafete_page_parser.parsePage(LINK_ERBA_CAFETE))
|
||||
writeStudentenwerkDataInDB(cafete_page_parser.parsePage(LINK_MARKUS_CAFETE))
|
||||
writeFekideDataInDB(fekide_happyhour_page_parser.parsePage(LINK_FEKIDE_GUIDE))
|
||||
writeStudentenwerkDataInDB(cafete_page_parser.parse_page(LINK_ERBA_CAFETE))
|
||||
writeStudentenwerkDataInDB(cafete_page_parser.parse_page(LINK_MARKUS_CAFETE))
|
||||
writeFekideDataInDB(fekide_happyhour_page_parser.parse_page(LINK_FEKIDE_GUIDE))
|
||||
|
||||
print("Neuer Stand:")
|
||||
writeoutDBObjects()
|
||||
logger.info("Neuer Stand:" + writeoutDBObjects())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
import requests
|
||||
|
||||
|
||||
def load_page(url: str):
|
||||
response = requests.get(url)
|
||||
if not response.ok:
|
||||
raise ConnectionError("Response not ok", response.status_code, url)
|
||||
return response.content
|
||||
@ -1,23 +1,22 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import json
|
||||
import datetime
|
||||
import logging
|
||||
import re
|
||||
from pprint import pprint
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from . import load_page
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SPEISEPLAN_NAME_SELECTOR = '.csc-default .csc-header .csc-firstHeader'
|
||||
|
||||
|
||||
def loadPage(url: str):
|
||||
return requests.get(url).content
|
||||
|
||||
|
||||
def getFoodplanName(soup):
|
||||
def get_foodplan_name(soup):
|
||||
foodplan_name = soup.select(SPEISEPLAN_NAME_SELECTOR)[0].getText()
|
||||
return foodplan_name
|
||||
|
||||
|
||||
def getRightLine(lines):
|
||||
def get_right_line(lines):
|
||||
foodlines = []
|
||||
pattern = re.compile("[0-9]+.+[A-Z]+")
|
||||
for line in list(lines):
|
||||
@ -27,42 +26,42 @@ def getRightLine(lines):
|
||||
return foodlines
|
||||
|
||||
|
||||
def getFoodPerDay(soup):
|
||||
def get_food_per_day(soup):
|
||||
days = []
|
||||
lines = soup.select('.csc-default .bodytext')
|
||||
foodlines = getRightLine(lines)
|
||||
foodlines = get_right_line(lines)
|
||||
for food in foodlines:
|
||||
dayObj = {}
|
||||
day = str(food).split()[0]
|
||||
foodName = str(food).replace(day, "").strip()
|
||||
singleFoodObj = {}
|
||||
singleFoodObj['title'] = foodName
|
||||
dayObj['date'] = day
|
||||
dayObj['menu'] = [singleFoodObj]
|
||||
days.append(dayObj)
|
||||
food_name = str(food).replace(day, "").strip()
|
||||
single_food_obj = {'title': food_name}
|
||||
day_obj = {
|
||||
'date': day,
|
||||
'menu': [single_food_obj]
|
||||
}
|
||||
days.append(day_obj)
|
||||
return days
|
||||
|
||||
|
||||
def parsePage(url: str):
|
||||
def parse_page(url: str):
|
||||
pagecontent = {}
|
||||
# {mensaspeiseplan:
|
||||
# {name:"",
|
||||
# weekmenu: [day:{date:, menu:[,,,]}]
|
||||
# }
|
||||
# }
|
||||
try:
|
||||
page = load_page(url)
|
||||
soup = BeautifulSoup(page, "lxml")
|
||||
foodplan_name = get_foodplan_name(soup)
|
||||
|
||||
page = loadPage(url)
|
||||
mensaSpeiseplan = {}
|
||||
soup = BeautifulSoup(page, "lxml")
|
||||
foodplan_name = getFoodplanName(soup)
|
||||
|
||||
days = getFoodPerDay(soup)
|
||||
mensaSpeiseplan['weekmenu'] = days
|
||||
mensaSpeiseplan['name'] = foodplan_name
|
||||
mensaSpeiseplan['execution_time'] = datetime.datetime.today().strftime("%A, %d.%m.%Y")
|
||||
mensaSpeiseplanJson = json.dumps(mensaSpeiseplan)
|
||||
return mensaSpeiseplanJson
|
||||
|
||||
days = get_food_per_day(soup)
|
||||
return {
|
||||
'weekmenu': days,
|
||||
'name': foodplan_name,
|
||||
'execution_time': datetime.datetime.today().strftime("%A, %d.%m.%Y")
|
||||
}
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return None
|
||||
|
||||
# LINK_ERBA_CAFETE = "https://www.studentenwerk-wuerzburg.de/bamberg/essen-trinken/sonderspeiseplaene/cafeteria-erba-insel.html"
|
||||
# pprint(parsePage(LINK_ERBA_CAFETE))
|
||||
|
||||
@ -1,51 +1,53 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from . import load_page
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SPEISEPLAN_NAME_SELECTOR = '.csc-default .csc-header .csc-firstHeader'
|
||||
|
||||
|
||||
def loadPage(url: str):
|
||||
return requests.get(url).content
|
||||
|
||||
|
||||
def getDay():
|
||||
def get_day():
|
||||
return datetime.datetime.today().strftime("%A, %d.%m.%Y")
|
||||
|
||||
|
||||
def getHappyHours(soup):
|
||||
def get_happy_hours(soup):
|
||||
happyhours = []
|
||||
happyhourstable = soup.select('#food .table tr')
|
||||
for tableline in happyhourstable:
|
||||
happyhour = {}
|
||||
linesoup = BeautifulSoup(str(tableline), "lxml")
|
||||
location = linesoup.find("td", {"class": "location"}).getText()
|
||||
time = linesoup.find("td", {"class": "time"}).getText()
|
||||
description = linesoup.find("td", {"class": "description"}).getText()
|
||||
description = str(description).strip()
|
||||
|
||||
happyhour['location'] = location
|
||||
happyhour['time'] = time
|
||||
happyhour['description'] = description
|
||||
happyhour = {
|
||||
'location': location,
|
||||
'time': time,
|
||||
'description': description
|
||||
}
|
||||
happyhours.append(happyhour)
|
||||
return happyhours
|
||||
|
||||
|
||||
def parsePage(url: str):
|
||||
pagecontent = {}
|
||||
def parse_page(url: str):
|
||||
# {
|
||||
# happyhours:[{happyhour:{location: "",time: "",description: ""},,,,]
|
||||
# }
|
||||
happyhours = []
|
||||
|
||||
page = loadPage(url)
|
||||
soup = BeautifulSoup(page, "lxml")
|
||||
happyhours = getHappyHours(soup)
|
||||
pagecontent['happyhours'] = happyhours
|
||||
pagecontent['day'] = getDay()
|
||||
pagecontent['execution_time'] = datetime.datetime.today().strftime("%A, %d.%m.%Y")
|
||||
return pagecontent
|
||||
|
||||
try:
|
||||
page = load_page(url)
|
||||
soup = BeautifulSoup(page, "lxml")
|
||||
happyhours = get_happy_hours(soup)
|
||||
return {
|
||||
'happyhours': happyhours,
|
||||
'day': get_day(),
|
||||
'execution_time': datetime.datetime.today().strftime("%A, %d.%m.%Y")
|
||||
}
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return None
|
||||
# LINK_FEKIDE_GUIDE = "https://www.feki.de/happyhour/wochenuebersicht"
|
||||
# parsePage(LINK_FEKIDE_GUIDE)
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import json
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
# FEKI_URL = "https://www.studentenwerk-wuerzburg.de/bamberg/essen-trinken/speiseplaene.html?tx_thmensamenu_pi2%5Bmensen%5D=3&tx_thmensamenu_pi2%5Baction%5D=show&tx_thmensamenu_pi2%5Bcontroller%5D=Speiseplan&cHash=c3fe5ebb35e5fba3794f01878e798b7c"
|
||||
from . import load_page
|
||||
|
||||
|
||||
def loadPage(url: str):
|
||||
return requests.get(url).content
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def getMenuDay(soup):
|
||||
@ -18,12 +15,10 @@ def getMenuDay(soup):
|
||||
def getFoodPerDay(soup):
|
||||
week_menus = []
|
||||
for day in soup.select('.currentweek .day'):
|
||||
menu = {}
|
||||
daysoup = BeautifulSoup(str(day), "lxml")
|
||||
day = getMenuDay(daysoup)
|
||||
day_menu = []
|
||||
for singleFood in daysoup.select('.menuwrap .menu'):
|
||||
singleFoodObj = {}
|
||||
singleFoodSoup = BeautifulSoup(str(singleFood), "lxml")
|
||||
title = singleFoodSoup.find('div', {'class': 'title'}).getText()
|
||||
allergens = [e.getText() for e in singleFoodSoup.select('.left .additnr .toggler ul li')]
|
||||
@ -34,13 +29,16 @@ def getFoodPerDay(soup):
|
||||
prices['price_employee'] = singleFoodSoup.select('.price')[0]['data-bed']
|
||||
if singleFoodSoup.select('.price'):
|
||||
prices['price_guest'] = singleFoodSoup.select('.price')[0]['data-guest']
|
||||
singleFoodObj['title'] = title
|
||||
singleFoodObj['allergens'] = allergens
|
||||
singleFoodObj['prices'] = prices
|
||||
day_menu.append(singleFoodObj)
|
||||
|
||||
menu['date'] = str(day).split(" ")[1]
|
||||
menu['menu'] = day_menu
|
||||
single_food_obj = {
|
||||
'title': title,
|
||||
'allergens': allergens,
|
||||
'prices': prices
|
||||
}
|
||||
day_menu.append(single_food_obj)
|
||||
menu = {
|
||||
'date': str(day).split(" ")[1],
|
||||
'menu': day_menu
|
||||
}
|
||||
week_menus.append(menu)
|
||||
return week_menus
|
||||
|
||||
@ -52,16 +50,19 @@ def parsePage(url: str):
|
||||
# weekmenu: [day:{date:, menu:[,,,]}]
|
||||
# }
|
||||
# }
|
||||
mensaSpeiseplan = {}
|
||||
page = loadPage(url)
|
||||
soup = BeautifulSoup(page, "lxml")
|
||||
foodplan_name = getFoodplanName(soup)
|
||||
days = getFoodPerDay(soup)
|
||||
mensaSpeiseplan['weekmenu'] = days
|
||||
mensaSpeiseplan['name'] = foodplan_name
|
||||
mensaSpeiseplan['execution_time'] = datetime.datetime.today().strftime("%A, %d.%m.%Y")
|
||||
mensaSpeiseplanJson = json.dumps(mensaSpeiseplan)
|
||||
return mensaSpeiseplanJson
|
||||
try:
|
||||
page = load_page(url)
|
||||
soup = BeautifulSoup(page, "lxml")
|
||||
foodplan_name = getFoodplanName(soup)
|
||||
days = getFoodPerDay(soup)
|
||||
return {
|
||||
'weekmenu': days,
|
||||
'name': foodplan_name,
|
||||
'execution_time': datetime.datetime.today().strftime("%A, %d.%m.%Y")
|
||||
}
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return None
|
||||
|
||||
|
||||
def getFoodplanName(soup):
|
||||
|
||||
@ -7,7 +7,7 @@ from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
|
||||
from apps.food.forms import UploadImageForm
|
||||
from apps.food.models import Menu, HappyHour, SingleFood, UserRating, UserFoodImage, FoodImage
|
||||
from apps.food.models import Menu, HappyHour, SingleFood, UserFoodRating, UserFoodImage, FoodImage
|
||||
|
||||
|
||||
# Create your views here.
|
||||
@ -100,12 +100,12 @@ def food_rating(request):
|
||||
rating = request.GET.get('rating', None)
|
||||
if food_id and rating:
|
||||
food = SingleFood.objects.get(id=food_id)
|
||||
user_rating, created = UserRating.objects.get_or_create(user=request.user,
|
||||
food=food)
|
||||
user_rating, created = UserFoodRating.objects.get_or_create(user=request.user,
|
||||
food=food)
|
||||
user_rating.rating = rating
|
||||
user_rating.save()
|
||||
|
||||
food_user_ratings = UserRating.objects.all().filter(food=food)
|
||||
food_user_ratings = UserFoodRating.objects.all().filter(food=food)
|
||||
sum = 0
|
||||
for food_user_rating in food_user_ratings:
|
||||
sum += food_user_rating.rating
|
||||
|
||||
0
ofu_app/apps/registration/api/__init__.py
Normal file
0
ofu_app/apps/registration/api/__init__.py
Normal file
55
ofu_app/apps/registration/api/serializers.py
Normal file
55
ofu_app/apps/registration/api/serializers.py
Normal file
@ -0,0 +1,55 @@
|
||||
from rest_framework import serializers
|
||||
from django.contrib.auth.models import User
|
||||
from apps.food.models import UserFoodRating, UserFoodImage, UserFoodComment, SingleFood, FoodImage
|
||||
from apps.food.api.v1_2.serializers.main_serializers import MinimalSingleFoodSerializer
|
||||
|
||||
|
||||
class FoodImageSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = FoodImage
|
||||
fields = ('image', 'thumb')
|
||||
|
||||
|
||||
class UserFoodImageSerializer(serializers.HyperlinkedModelSerializer):
|
||||
image = FoodImageSerializer(many=False, read_only=True)
|
||||
food = MinimalSingleFoodSerializer(many=False, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = UserFoodImage
|
||||
fields = ('id', 'food', 'image')
|
||||
|
||||
|
||||
class SingleFoodSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = SingleFood
|
||||
fields = ('id', 'name')
|
||||
|
||||
|
||||
class UserFoodImagesSerializer(serializers.HyperlinkedModelSerializer):
|
||||
food = SingleFoodSerializer(many=False, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = UserFoodImage
|
||||
fields = ('id', 'food', 'image_image', 'image_thumb')
|
||||
|
||||
|
||||
class UserRatingSerializer(serializers.HyperlinkedModelSerializer):
|
||||
food = SingleFoodSerializer(many=False, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = UserFoodRating
|
||||
fields = ('id', 'food', 'rating')
|
||||
|
||||
|
||||
class UserCommentsSerializer(serializers.HyperlinkedModelSerializer):
|
||||
food = SingleFoodSerializer(many=False, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = UserFoodComment
|
||||
fields = ('id', 'food', 'description', 'title')
|
||||
|
||||
|
||||
class UserInformationSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('id', 'username', 'first_name', 'last_name', 'email', 'date_joined', 'last_login')
|
||||
26
ofu_app/apps/registration/api/urls.py
Normal file
26
ofu_app/apps/registration/api/urls.py
Normal file
@ -0,0 +1,26 @@
|
||||
"""ofu_app URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/1.11/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.conf.urls import url, include
|
||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf.urls import url
|
||||
from apps.registration.api import views as api_views
|
||||
from apps.food.models import Menu
|
||||
|
||||
urlpatterns = [
|
||||
# API Version 1.1
|
||||
url(r'^account/$', api_views.UserInformations.as_view(), name='api-v1_1-user-information'),
|
||||
url(r'^account/food/ratings/$', api_views.UserRatings.as_view(), name='api-v1_1-user-rating'),
|
||||
url(r'^account/food/images/$', api_views.UserImages.as_view(), name='api-v1_1-user-image'),
|
||||
url(r'^account/food/comments/$', api_views.UserComments.as_view(), name='api-v1_1-user-comment'),
|
||||
]
|
||||
71
ofu_app/apps/registration/api/views.py
Normal file
71
ofu_app/apps/registration/api/views.py
Normal file
@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from apps.food.models import UserFoodComment, UserFoodImage, UserFoodRating
|
||||
from apps.registration.api.serializers import UserInformationSerializer, UserRatingSerializer, UserFoodImageSerializer, \
|
||||
UserCommentsSerializer
|
||||
from rest_framework import generics
|
||||
from rest_framework.decorators import permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
|
||||
@permission_classes((IsAuthenticated,))
|
||||
class UserInformations(generics.ListAPIView):
|
||||
serializer_class = UserInformationSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return [self.request.user]
|
||||
|
||||
|
||||
@permission_classes((IsAuthenticated,))
|
||||
class UserRatings(generics.ListAPIView):
|
||||
serializer_class = UserRatingSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
food_id = self.request.query_params.get('food_id')
|
||||
queryset = UserFoodRating.objects.filter(user=user).order_by('food__name')
|
||||
if food_id:
|
||||
try:
|
||||
queryset = queryset.filter(food_id=food_id)
|
||||
except ValueError as e:
|
||||
# TODO: return Exception
|
||||
return []
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
@permission_classes((IsAuthenticated,))
|
||||
class UserImages(generics.ListAPIView):
|
||||
serializer_class = UserFoodImageSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
food_id = self.request.query_params.get('food_id')
|
||||
queryset = UserFoodImage.objects.filter(user=user).order_by('food__name')
|
||||
if food_id:
|
||||
try:
|
||||
queryset = queryset.filter(food_id=food_id)
|
||||
except ValueError as e:
|
||||
# TODO: return Exception
|
||||
return []
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
@permission_classes((IsAuthenticated,))
|
||||
class UserComments(generics.ListAPIView):
|
||||
serializer_class = UserCommentsSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
food_id = self.request.query_params.get('food_id')
|
||||
queryset = UserFoodComment.objects.filter(user=user).order_by('food__name')
|
||||
if food_id:
|
||||
try:
|
||||
queryset = queryset.filter(food_id=food_id)
|
||||
except ValueError as e:
|
||||
# TODO: return Exception
|
||||
return []
|
||||
|
||||
return queryset
|
||||
@ -8,4 +8,5 @@ urlpatterns = [
|
||||
url(r'^account_activation_sent/$', core_views.account_activation_sent, name='account_activation_sent'),
|
||||
url(r'^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
|
||||
core_views.activate, name='activate'),
|
||||
url(r'^signup/$', core_views.signup, name='signup'),
|
||||
]
|
||||
|
||||
@ -11,7 +11,7 @@ from django.utils.encoding import force_text
|
||||
from django.utils.http import urlsafe_base64_decode
|
||||
from django.core.mail import send_mail
|
||||
from django.shortcuts import HttpResponse, redirect
|
||||
from apps.food.models import UserRating, UserFoodImage
|
||||
from apps.food.models import UserFoodRating, UserFoodImage
|
||||
|
||||
|
||||
def signup(request):
|
||||
@ -22,7 +22,7 @@ def signup(request):
|
||||
user.is_active = False
|
||||
user.save()
|
||||
current_site = request.META['HTTP_HOST']
|
||||
subject = 'Activate Your MySite Account'
|
||||
subject = 'Activate Your BaStA Account'
|
||||
message = render_to_string('registration/account_activation_email.jinja', {
|
||||
'user': user,
|
||||
'domain': current_site,
|
||||
@ -64,7 +64,7 @@ def account_view(request):
|
||||
if request.user.is_authenticated:
|
||||
user = request.user
|
||||
|
||||
food_ratings = UserRating.objects.filter(user=user).order_by('food__name')
|
||||
food_ratings = UserFoodRating.objects.filter(user=user).order_by('food__name')
|
||||
food_images = UserFoodImage.objects.filter(user=user.id)
|
||||
print(food_images)
|
||||
|
||||
|
||||
@ -11,132 +11,48 @@ https://docs.djangoproject.com/en/1.11/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
import datetime
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = os.environ['SECRET_KEY']
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = bool(os.environ.get('DEBUG', False))
|
||||
ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split()
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
'django_jinja',
|
||||
'apps.food',
|
||||
'apps.events',
|
||||
'apps.donar',
|
||||
'apps.registration',
|
||||
'rest_framework',
|
||||
'analytical',
|
||||
]
|
||||
DOMAIN = os.environ['DOMAIN']
|
||||
|
||||
SITE_NAME = os.environ['SITE_NAME']
|
||||
SITE_ID = 1
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
],
|
||||
'PAGE_SIZE': 10
|
||||
}
|
||||
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',
|
||||
]
|
||||
ADMINS = os.environ['ADMINS'].split()
|
||||
|
||||
ROOT_URLCONF = 'core.urls'
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django_jinja.backend.Jinja2',
|
||||
'DIRS': [
|
||||
os.path.join(BASE_DIR, 'templates'),
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'environment': 'core.jinja2.environment'
|
||||
},
|
||||
},
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR,'templates')],
|
||||
'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',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
WSGI_APPLICATION = 'core.wsgi.application'
|
||||
SECRET_KEY = os.environ['SECRET_KEY']
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||
DEBUG = bool(os.environ.get('DEBUG', False))
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': os.environ.get('POSTGRES_USER', ''),
|
||||
'USER': os.environ.get('POSTGRES_USER', ''),
|
||||
'PASSWORD': os.environ.get('POSTGRES_PASSWORD', ''),
|
||||
'HOST': os.environ.get('DATABASE_HOST', ''),
|
||||
'PORT': os.environ.get('DATABASE_PORT', ''),
|
||||
},
|
||||
'dev': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split()
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
||||
# Sign Up E-Mail authentication
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = os.environ['EMAIL_HOST']
|
||||
EMAIL_HOST_USER = os.environ['EMAIL_HOST_USER']
|
||||
EMAIL_HOST_PASSWORD = os.environ['EMAIL_HOST_PASSWORD']
|
||||
EMAIL_PORT = os.environ['EMAIL_PORT']
|
||||
EMAIL_USE_TLS = True
|
||||
|
||||
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',
|
||||
},
|
||||
]
|
||||
# TODO: more account with same email are possible?
|
||||
ACCOUNT_EMAIL_UNIQUE = True
|
||||
ACCOUNT_EMAIL_CONFIRMATION_REQUIRED = True
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
# Setup support for proxy headers
|
||||
USE_X_FORWARDED_HOST = True
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
|
||||
# Media files should be stored here
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
# monitoring
|
||||
PIWIK_DOMAIN_PATH = os.environ['PIWIK_DOMAIN_PATH']
|
||||
PIWIK_SITE_ID = os.environ['PIWIK_SITE_ID']
|
||||
|
||||
LOGIN_REDIRECT_URL = 'home'
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
||||
@ -155,38 +71,179 @@ DATE_FORMAT = "l, d. F Y"
|
||||
DATETIME_FORMAT = "l, d. F Y"
|
||||
TIME_FORMAT = "H:i"
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
||||
ROOT_URLCONF = 'core.urls'
|
||||
WSGI_APPLICATION = 'core.wsgi.application'
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "static_files")
|
||||
print(STATIC_ROOT)
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "static"),
|
||||
]
|
||||
|
||||
# Setup support for proxy headers
|
||||
USE_X_FORWARDED_HOST = True
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
# CORS
|
||||
CORS_ORIGIN_ALLOW_ALL = False
|
||||
|
||||
# FORCE_SCRIPT_NAME = "app"
|
||||
# Media files should be stored here
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
MEDIA_URL = '/media/'
|
||||
CORS_ORIGIN_WHITELIST = (
|
||||
'localhost:3000',
|
||||
)
|
||||
|
||||
# monitoring
|
||||
PIWIK_DOMAIN_PATH = 'mg-server.ddns.net/piwik'
|
||||
PIWIK_SITE_ID = '1'
|
||||
# Application definition
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
'django_jinja',
|
||||
'apps.food',
|
||||
'apps.events',
|
||||
'apps.donar',
|
||||
'apps.registration',
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
'djoser',
|
||||
'analytical',
|
||||
'corsheaders',
|
||||
]
|
||||
|
||||
LOGIN_REDIRECT_URL = 'home'
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework.authentication.BasicAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
),
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
],
|
||||
}
|
||||
DJOSER = {
|
||||
'SEND_ACTIVATION_EMAIL': True,
|
||||
'ACTIVATION_URL': os.environ['ACTIVATION_URL'],
|
||||
'SET_PASSWORD_RETYPE': True,
|
||||
'PASSWORD_RESET_CONFIRM_RETYPE': True,
|
||||
'PASSWORD_RESET_CONFIRM_URL': os.environ['PASSWORD_RESET_CONFIRM_URL'],
|
||||
'PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND': True,
|
||||
}
|
||||
MIDDLEWARE = [
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'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',
|
||||
]
|
||||
|
||||
# Sign Up E-Mail authentication
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = 'smtp.gmail.com'
|
||||
EMAIL_HOST_USER = 'signup.basta@gmail.com'
|
||||
EMAIL_HOST_PASSWORD = '1\SL^QzlSuP<`8gkP4Fd'
|
||||
EMAIL_PORT = '587'
|
||||
EMAIL_USE_TLS = True
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
|
||||
ACCOUNT_EMAIL_UNIQUE = True
|
||||
ACCOUNT_EMAIL_CONFIRMATION_REQUIRED = True
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django_jinja.backend.Jinja2',
|
||||
'DIRS': [
|
||||
os.path.join(BASE_DIR, 'templates'),
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'environment': 'core.jinja2.environment'
|
||||
},
|
||||
},
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||
'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',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': os.environ.get('POSTGRES_USER', ''),
|
||||
'USER': os.environ.get('POSTGRES_USER', ''),
|
||||
'PASSWORD': os.environ.get('POSTGRES_PASSWORD', ''),
|
||||
'HOST': os.environ.get('DATABASE_HOST', ''),
|
||||
'PORT': os.environ.get('DATABASE_PORT', ''),
|
||||
},
|
||||
'dev': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.11/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',
|
||||
},
|
||||
]
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'default': {
|
||||
'format': '%(asctime)s %(module)s [%(levelname)s]: %(message)s',
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'default',
|
||||
},
|
||||
'file': {
|
||||
'class': 'logging.FileHandler',
|
||||
'filename': '/log/import_food.log',
|
||||
'formatter': 'default',
|
||||
},
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'apps.food.utils': {
|
||||
'handlers': ['console', 'file', 'mail_admins'],
|
||||
'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),
|
||||
},
|
||||
'apps.donar.utils': {
|
||||
'handlers': ['console', 'file', 'mail_admins'],
|
||||
'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -13,11 +13,13 @@ Including another URLconf
|
||||
1. Import the include() function: from django.conf.urls import url, include
|
||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
||||
"""
|
||||
|
||||
from core import views
|
||||
from django.conf.urls import url, include
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import views as auth_views
|
||||
from rest_framework import routers
|
||||
from rest_framework.authtoken import views as token_auth_views
|
||||
from apps.food import urls as food_urls
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
@ -46,7 +48,18 @@ urlpatterns = [
|
||||
url(r'^impressum/$', views.impressum, name='impressum'),
|
||||
|
||||
# -- API --
|
||||
# -- Version 1.0
|
||||
url(r'^api/v1/', include(api_router_v1.urls)),
|
||||
url(r'^api/v1.1/', include('apps.food.api.urls')),
|
||||
url(r'^api/auth/', include('rest_framework.urls', namespace='rest_framework'))
|
||||
# -- Version 1.1
|
||||
url(r'^api/v1.1/', include('apps.food.api.v1_1.urls')),
|
||||
url(r'^api/v1.1/', include('apps.registration.api.urls')),
|
||||
|
||||
# -- Version 1.2
|
||||
url(r'^api/v1.2/', include('apps.food.api.v1_2.urls')),
|
||||
url(r'^api/v1.2/', include('apps.registration.api.urls')),
|
||||
|
||||
# -- Third Party APIs
|
||||
url(r'^api/auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
url(r'^api/token-auth/', include('djoser.urls')),
|
||||
url(r'^api/token-auth/', include('djoser.urls.authtoken')),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
django==2.0.1
|
||||
django-jinja==2.4.1
|
||||
django-rest-framework==0.1.0
|
||||
#django-rest-auth==0.9.3
|
||||
#djangorestframework-jwt==1.11.0
|
||||
#django-allauth==0.35.0
|
||||
django-analytical==2.4.0
|
||||
requests==2.18.4
|
||||
beautifulsoup4==4.6.0
|
||||
#psycopg2==2.7.3.2
|
||||
xmltodict==0.11.0
|
||||
coverage==3.6
|
||||
|
||||
django-cors-headers
|
||||
djoser==1.1.5
|
||||
6
ofu_app/static/css/main.css
Normal file
6
ofu_app/static/css/main.css
Normal file
@ -0,0 +1,6 @@
|
||||
.disabled, .disabled:hover {
|
||||
color: #999999;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
text-decoration: none;
|
||||
}
|
||||
@ -8,11 +8,18 @@ document.addEventListener('DOMContentLoaded', rate_init);
|
||||
*/
|
||||
function rate_init() {
|
||||
add_Stars();
|
||||
$('.star').on("mouseenter mouseleave", function () {
|
||||
$('.star').on("mouseenter", function () {
|
||||
showRating(this);
|
||||
}).on("click", function () {
|
||||
console.log('Click');
|
||||
sendRating(this);
|
||||
}).on("mouseleave", function () {
|
||||
var rating = $(this).parent().parent().parent().parent().parent().data('rating');
|
||||
var food_id = $(this).attr('class').split(' ')[0].split('-')[2];
|
||||
buildRating(food_id, rating);
|
||||
console.log("leaved");
|
||||
console.log("rating" + $(this).parent().parent().parent().parent().parent().data('rating'));
|
||||
console.log("food_id" + food_id);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
<link rel="stylesheet" href="{{ static('libs/font-awesome-4.7.0/css/font-awesome.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('libs/bootstrap-4.0.0-beta-dist/css/bootstrap.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('css/nav.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('css/main.css') }}">
|
||||
{% block css_extra %}{% endblock %}
|
||||
<!-- Piwik -->
|
||||
<script type="text/javascript">
|
||||
@ -95,7 +96,7 @@
|
||||
</div>
|
||||
<div class="row text-center bg-dark text-white pb-2">
|
||||
<div class="col">
|
||||
© Copyright 2017, Michael Götz
|
||||
© Copyright 2018, Michael Götz
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -18,5 +18,4 @@
|
||||
{# {{ macros.happy_hours(happy_hours=happy_hours) }}#}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@ -9,10 +9,15 @@
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row text-dark">
|
||||
{{ macros.home_item_with_icon(icon='fa-cutlery', url_id='daily-food', title='Food') }}
|
||||
{# {{ macros.home_item_with_icon(icon='fa-cutlery', url_id='daily-food', title='Food') }}
|
||||
{{ macros.home_item_with_icon(icon='fa-calendar-o', url_id='day-events', title='Events') }}
|
||||
{{ macros.home_item_with_icon(icon='fa-compass', url_id='donar', title='Nav') }}
|
||||
{{ macros.home_item_with_icon(icon='fa-th-large', url_id='links-home', title='Links') }}
|
||||
{{ macros.home_item_with_icon(icon='fa-th-large', url_id='links-home', title='Links') }}#}
|
||||
|
||||
{{ macros.home_item_with_icon(icon='fa-cutlery', url_id='daily-food', title='Food') }}
|
||||
{{ macros.home_item_with_icon(icon='fa-calendar-o',link='', title='Events', attr='class=disabled') }}
|
||||
{{ macros.home_item_with_icon(icon='fa-compass',link='', title='Nav', attr='class=disabled') }}
|
||||
{{ macros.home_item_with_icon(icon='fa-th-large',link='', title='Links', attr='class=disabled') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -59,7 +59,6 @@
|
||||
{% for single_food in menu %}
|
||||
<li data-food="{{ single_food.id }}" data-rating="{{ single_food.rating }}" class="food-item media mb-2">
|
||||
<div class="mr-2 media-left media-middle">
|
||||
{# TODO: without many to many #}
|
||||
{% if single_food.image %}
|
||||
<a href="{{ single_food.image.image.url }}" data-lightbox="{{ title }}"
|
||||
data-title="{{ single_food.name }}">
|
||||
|
||||
@ -11,11 +11,11 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro home_item_with_icon(icon, icon_size='fa-4x', url_id='', link='', title='') -%}
|
||||
{% macro home_item_with_icon(icon, icon_size='fa-4x', url_id='', link='', title='', attr='') -%}
|
||||
<div class="col-12 col-sm-6 col-lg-6 col-xl-6 p-3">
|
||||
<div class="card">
|
||||
{% if url_id %}
|
||||
<a href="{{ url(url_id) }}">
|
||||
<a href="{{ url(url_id) }}" {{ attr }}>
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-center"><i class="fa {{ icon }} {{ icon_size }}"
|
||||
aria-hidden="true"></i></h4>
|
||||
@ -25,7 +25,7 @@
|
||||
</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ link }}">
|
||||
<a href="{{ link }}" {{ attr }}>
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-center"><i class="fa {{ icon }} {{ icon_size }}"
|
||||
aria-hidden="true"></i></h4>
|
||||
|
||||
@ -8,15 +8,22 @@
|
||||
<label for="username" class="label">Username:</label>
|
||||
<p>
|
||||
<input id="username" type="text" name="username" required autofocus maxlength="150" id="id_username"/>
|
||||
<small style="color: grey">Erforderlich. 150 Zeichen oder weniger. Nur Buchstaben, Ziffern und
|
||||
@/./+/-/_.
|
||||
<small style="color: grey">Erforderlich.
|
||||
<ul>
|
||||
<li>Nur Buchstaben,</li>
|
||||
<li>Ziffern und @/./+/-/_.</li>
|
||||
</ul>
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<label for="email" class="label">E-Mail:</label>
|
||||
<p>
|
||||
<input id="email" type="email" name="email" required maxlength="254" id="id_email"/>
|
||||
<small style="color: grey">Required. Inform a valid email address.</small>
|
||||
<small style="color: grey">Erforderlich.
|
||||
<ul>
|
||||
<li>Bitte beachten Sie, dass aktuell keine Stud Mail Accounts unterstützt werden können.</li>
|
||||
</ul>
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<label for="password1" class="label">Password:</label>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,22 +0,0 @@
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'Liederabend mit Martin Fösel (Gesang) und Beate Roux (Klavier)'
|
||||
('Internationale Konferenz zum Ganztag "WERA-IRN Konferenz Ganztägige Bildung '
|
||||
'aus einer international vergleichenden Perspektive", 30.11.- 02.12.17')
|
||||
'11. Bamberger Neuropsychologietag'
|
||||
('Vortrag Dr. Jessica Röhner: Die Untersuchung von fälschungs- und '
|
||||
'konstruktbezogener Varianz im IAT mit Hilfe von Diffusionsmodellanalysen')
|
||||
('Vortrag Dr. Anna Dechant: (Nicht-)Intentional Partnerlos. Wer ist '
|
||||
'(un-)freiwillig Single und wie verändert sich das mit der Zeit?')
|
||||
('Vortrag Dr. Oliver Arnold: Verhalten als kompensatorische Funktion von '
|
||||
'Einstellung und Verhaltenskosten: Die Person-Situation-Interaktion im Rahmen '
|
||||
'des Campell-Paradigmas')
|
||||
('Vortrag Prof. Dr. Christine Syrek: Mikropause bis Urlaub: Förderung von '
|
||||
'Gesundheit und Leistungsverhalten durch Erholung von arbeitsbezogenem Stress')
|
||||
('Vortrag PD Dr. Miriam Kunz: Wenn die Sprache versiegt: Affekterkennung bei '
|
||||
'Demenz')
|
||||
'Hochschulöffentliches Gespräch mit dem Mittelbau'
|
||||
'Hochschulöffentliches Gespräch mit Studierenden'
|
||||
Successfully migrate data
|
||||
@ -1,99 +0,0 @@
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'Abschlussworkshop Innovationslabor'
|
||||
'Auftaktworkshop Innovationslabor'
|
||||
'Bigband Aufbau'
|
||||
'Cajónbau Workshop'
|
||||
'Cajónbau Workshop'
|
||||
'Cajón Workshop'
|
||||
'Ensembleleitung Übung'
|
||||
'Fortbildungstag "Musik lebendig unterrichten"'
|
||||
'Fortbildungstag "Musik lebendig unterrichten"'
|
||||
'Fortbildungstag "Musik lebendig unterrichten"'
|
||||
'Fortbildungstag "Musik lebendig unterrichten"'
|
||||
'Kammerorchesterprobe'
|
||||
'Kammerorchesterprobe'
|
||||
'Kammerorchesterprobe'
|
||||
'Kammerorchesterprobe'
|
||||
'Kammerorchesterprobe'
|
||||
'Liederabend mit Martin Fösel (Gesang) und Beate Roux (Klavier)'
|
||||
'Probe für Liederabend mit Martin Fösel (Gesang) Beate Roux (Klavier) und'
|
||||
'Semester-Ouvertüre'
|
||||
'Staatsexamen Ensemblearbeit'
|
||||
'Aktivierende Methoden'
|
||||
'Aktivierende Methoden'
|
||||
'Andragogentag'
|
||||
'Visualisieren - Präsentieren'
|
||||
'Doktoranden-Kolloquium'
|
||||
('Internationale Konferenz zum Ganztag "WERA-IRN Konferenz Ganztägige Bildung '
|
||||
'aus einer international vergleichenden Perspektive", 30.11.- 02.12.17')
|
||||
'Tagung - WERA-IRN Extended Education'
|
||||
'Bamberger Peer-Beratungstraining'
|
||||
'Bamberger Peer-Beratungstraining'
|
||||
'Bamberger Peer-Beratungstraining'
|
||||
'Bamberger Peer-Beratungstraining'
|
||||
'Bamberger Peer-Beratungstraining'
|
||||
'Bamberger Peer-Beratungstraining'
|
||||
'Bamberger Peer-Beratungstraining'
|
||||
'Bamberger Peer-Beratungstraining'
|
||||
'Bamberger Peer-Beratungstraining'
|
||||
'Bamberger Peer-Beratungstraining'
|
||||
'Beratung im schulischen Kontext. Das Bamberger Peer-Beratungstraining'
|
||||
'Beratung im schulischen Kontext. Das Bamberger Peer-Beratungstraining'
|
||||
'Beratung im schulischen Kontext. Das Bamberger Peer-Beratungstraining'
|
||||
('Jahrestagung 2018 der Konferenz für Grundschulpädagogik und -didaktik an '
|
||||
'bayerischen Universitäten')
|
||||
'Lernwerkstattfortbildung für Schulleitungen'
|
||||
'Lernwerkstattfortbildung für Schulleitungen'
|
||||
'Lernwerkstattfortbildung für Schulleitungen'
|
||||
'Lernwerkstattfortbildung für Schulleitungen'
|
||||
'Lehrstuhlkolloquium'
|
||||
'Der Masterstudiengang Gerontologie in Erlangen'
|
||||
'Gruppentraining Sozialer Kompetenzen'
|
||||
'Infoabend Auslandssemester/-praktikum'
|
||||
'Nebenfachabend'
|
||||
'Pädagogik in einem (sozial-) psychiatrischen Arbeitsfeld'
|
||||
'Prüfungsangstbewältigung'
|
||||
'Psychische Erkrankungen bei Studierenden'
|
||||
'Schriftl. Prüfung "Masterstudiengang Educational Quality"'
|
||||
'Schulleitersymposium (SLS)'
|
||||
'Schulleitersymposium (SLS)'
|
||||
'Schulleitersymposium (SLS)'
|
||||
'Schulleitersymposium (SLS)'
|
||||
'Schulleitersymposium (SLS)'
|
||||
'Schwerpunktabend Sozialpädagogik'
|
||||
'Schwerpunktabend EFP'
|
||||
'Schwerpunktabend Erwachsenenbildung/Weiterbildung'
|
||||
'Forschertreffen'
|
||||
'Forschertreffen'
|
||||
'Forschertreffen'
|
||||
'Frau Penczek'
|
||||
'Jour fixe'
|
||||
'Jour fixe'
|
||||
'Praxis lehren'
|
||||
'Praxis lehren'
|
||||
'Workshop Bildung'
|
||||
'Workshop Bildung'
|
||||
'Workshop Bildung'
|
||||
'11. Bamberger Neuropsychologietag'
|
||||
'Disputation'
|
||||
'Training zur psychischen ersten Hilfe für Laien'
|
||||
('Vortrag Dr. Jessica Röhner: Die Untersuchung von fälschungs- und '
|
||||
'konstruktbezogener Varianz im IAT mit Hilfe von Diffusionsmodellanalysen')
|
||||
('Vortrag Dr. Anna Dechant: (Nicht-)Intentional Partnerlos. Wer ist '
|
||||
'(un-)freiwillig Single und wie verändert sich das mit der Zeit?')
|
||||
('Vortrag Dr. Oliver Arnold: Verhalten als kompensatorische Funktion von '
|
||||
'Einstellung und Verhaltenskosten: Die Person-Situation-Interaktion im Rahmen '
|
||||
'des Campell-Paradigmas')
|
||||
('Vortrag Prof. Dr. Christine Syrek: Mikropause bis Urlaub: Förderung von '
|
||||
'Gesundheit und Leistungsverhalten durch Erholung von arbeitsbezogenem Stress')
|
||||
('Vortrag PD Dr. Miriam Kunz: Wenn die Sprache versiegt: Affekterkennung bei '
|
||||
'Demenz')
|
||||
'Abschiedsvorlesung Prof. Dr. Rahm (noch unter Vorbehalt)'
|
||||
'Disputation Nusser'
|
||||
'Fakultätsweihnacht'
|
||||
'Hochschulöffentliches Gespräch mit dem Mittelbau'
|
||||
'Hochschulöffentliches Gespräch mit Studierenden'
|
||||
Successfully migrate data
|
||||
@ -1,22 +0,0 @@
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'ForMaD: Forum Mathematik-Didaktik'
|
||||
'Liederabend mit Martin Fösel (Gesang) und Beate Roux (Klavier)'
|
||||
('Internationale Konferenz zum Ganztag "WERA-IRN Konferenz Ganztägige Bildung '
|
||||
'aus einer international vergleichenden Perspektive", 30.11.- 02.12.17')
|
||||
'11. Bamberger Neuropsychologietag'
|
||||
('Vortrag Dr. Jessica Röhner: Die Untersuchung von fälschungs- und '
|
||||
'konstruktbezogener Varianz im IAT mit Hilfe von Diffusionsmodellanalysen')
|
||||
('Vortrag Dr. Anna Dechant: (Nicht-)Intentional Partnerlos. Wer ist '
|
||||
'(un-)freiwillig Single und wie verändert sich das mit der Zeit?')
|
||||
('Vortrag Dr. Oliver Arnold: Verhalten als kompensatorische Funktion von '
|
||||
'Einstellung und Verhaltenskosten: Die Person-Situation-Interaktion im Rahmen '
|
||||
'des Campell-Paradigmas')
|
||||
('Vortrag Prof. Dr. Christine Syrek: Mikropause bis Urlaub: Förderung von '
|
||||
'Gesundheit und Leistungsverhalten durch Erholung von arbeitsbezogenem Stress')
|
||||
('Vortrag PD Dr. Miriam Kunz: Wenn die Sprache versiegt: Affekterkennung bei '
|
||||
'Demenz')
|
||||
'Hochschulöffentliches Gespräch mit dem Mittelbau'
|
||||
'Hochschulöffentliches Gespräch mit Studierenden'
|
||||
Successfully migrate data
|
||||
Reference in New Issue
Block a user