Merge branch 'master' of https://mg-server.ddns.net/git/BaStA/basta-server into app.wiai.de

This commit is contained in:
otrs 2018-04-03 13:12:42 +02:00
commit 34f5e39b83
70 changed files with 1817 additions and 3495 deletions

View File

@ -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"]

View File

@ -9,6 +9,7 @@ services:
volumes:
- ./data/data:/data
- ./data/media:/media
- ./log:/log
- ./ofu_app/:/app
env_file:
- docker.env

View 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()

View 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()

View 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),
),
]

View 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),
),
]

View 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),
),
]

View 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),
),
]

View 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),
),
]

View File

@ -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

View File

@ -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__':

View File

@ -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,
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__':

View File

@ -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"),
event_obj, _ = 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
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()

View File

@ -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)

View File

View 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 = [

View File

@ -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

View File

View 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')

View 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

View 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'),
]

View 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)

View 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()
#
#

View File

@ -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

View 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')},
),
]

View 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'),
),
]

View 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')},
),
]

View 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)]),
),
]

View 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,
),
]

View 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'),
),
]

View 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,
),
]

View 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'),
),
]

View 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'),
),
]

View 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',
),
]

View 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),
),
]

View File

@ -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))

View File

View 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>

View File

@ -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

View File

@ -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()

View File

@ -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)
if 'allergens' in single_food:
logger.info("{}".format(single_food['title']))
allergens = []
if 'allergens' in single_food:
for allergen in single_food['allergens']:
allergens.append(Allergene.objects.get_or_create(name=allergen)[0])
# TODO: Consider keyword arg for price
try:
allergens.append(Allergene.objects.create(name=allergen))
except IntegrityError:
allergens.append(Allergene.objects.get(name=allergen))
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'],
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())
if not new:
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__':

View File

@ -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

View File

@ -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:[,,,]}]
# }
# }
page = loadPage(url)
mensaSpeiseplan = {}
try:
page = load_page(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
foodplan_name = get_foodplan_name(soup)
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))

View File

@ -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)
try:
page = load_page(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
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)

View File

@ -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)
try:
page = load_page(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
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):

View File

@ -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,
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

View 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')

View 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'),
]

View 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

View File

@ -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'),
]

View File

@ -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)

View File

@ -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'),
},
},
}

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,6 @@
.disabled, .disabled:hover {
color: #999999;
cursor: not-allowed;
opacity: 0.5;
text-decoration: none;
}

View File

@ -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);
})
}

View File

@ -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>

View File

@ -18,5 +18,4 @@
{# {{ macros.happy_hours(happy_hours=happy_hours) }}#}
</div>
</div>
{% endblock %}

View File

@ -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 %}

View File

@ -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 }}">

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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