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 && \ RUN apk upgrade --update && \
apk add --update python3 py3-pillow py3-lxml py3-psycopg2 && \ apk add --update python3 py3-pillow py3-lxml py3-psycopg2 && \
pip3 install -r /requirements.txt && rm /requirements.txt pip3 install -r /requirements.txt && rm /requirements.txt
EXPOSE 80
WORKDIR /app WORKDIR /app
VOLUME ["/app"] VOLUME ["/app"]
VOLUME ["/app/data"] VOLUME ["/app/data"]
VOLUME ["/app/media"] VOLUME ["/app/media"]
VOLUME ["/app/log"]
ENTRYPOINT ["python3", "manage.py"] ENTRYPOINT ["python3", "manage.py"]
CMD ["runserver", "0.0.0.0:80"] CMD ["runserver", "0.0.0.0:80"]

View File

@ -9,6 +9,7 @@ services:
volumes: volumes:
- ./data/data:/data - ./data/data:/data
- ./data/media:/media - ./data/media:/media
- ./log:/log
- ./ofu_app/:/app - ./ofu_app/:/app
env_file: env_file:
- docker.env - 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_COORDS_LENGTH = 256
MAX_LECTURE_IDS_LENGTH = 256 MAX_LECTURE_IDS_LENGTH = 256
MAX_LECTURE_SHORT_LENGTH = 128 MAX_LECTURE_SHORT_LENGTH = 512
MAX_LECTURE_NAME_LENGTH = 256 MAX_LECTURE_NAME_LENGTH = 512
MAX_LECTURE_TYPE_LENGTH = 64 MAX_LECTURE_TYPE_LENGTH = 64

View File

@ -4,6 +4,9 @@ import json
from pprint import pprint from pprint import pprint
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from apps.donar.utils.parser import univis_lectures_parser from apps.donar.utils.parser import univis_lectures_parser
import logging
logger = logging.getLogger(__name__)
# CONFIG Fakultaet # CONFIG Fakultaet
FAKULTAET_GuK = "Fakult%E4t%20Geistes-%20und%20Kulturwissenschaften" FAKULTAET_GuK = "Fakult%E4t%20Geistes-%20und%20Kulturwissenschaften"
@ -32,38 +35,35 @@ def writeUnivisLectureTermsInDB(lecture, lecture_obj):
if type(lecture['terms']['term']) == list: if type(lecture['terms']['term']) == list:
for term in lecture['terms']['term']: for term in lecture['terms']['term']:
try: try:
term_obj = Lecture_Terms.objects.create()
starttime = "00:00" starttime = "00:00"
term_obj = Lecture_Terms.objects.create(starttime=starttime)
if 'starttime' in term: if 'starttime' in term:
starttime = term['starttime'] starttime = term['starttime']
term_obj.starttime = datetime.strptime(starttime, "%H:%M") term_obj.starttime = datetime.strptime(starttime, "%H:%M")
term_obj.save()
if 'room' in term: if 'room' in term:
room_id = term['room']['UnivISRef']['@key'] room_id = term['room']['UnivISRef']['@key']
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) lecture_obj.term.add(term_obj)
except IntegrityError as err: except IntegrityError as err:
print("ROOM_ID: " + str(room_id)) logger.exception(err)
print(err.args)
else: else:
try: try:
term_obj = Lecture_Terms.objects.create()
univis_starttime = "00:00" univis_starttime = "00:00"
term_obj = Lecture_Terms.objects.create(starttime=univis_starttime)
if 'starttime' in lecture['terms']['term']: if 'starttime' in lecture['terms']['term']:
univis_starttime = lecture['terms']['term']['starttime'] univis_starttime = lecture['terms']['term']['starttime']
term_obj.starttime = datetime.strptime(univis_starttime, '%H:%M') term_obj.starttime = datetime.strptime(univis_starttime, '%H:%M')
term_obj.save()
if 'room' in lecture['terms']['term']: if 'room' in lecture['terms']['term']:
room_id = lecture['terms']['term']['room']['UnivISRef']['@key'] room_id = lecture['terms']['term']['room']['UnivISRef']['@key']
pprint("Room: " + room_id)
Room.objects.get(key=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() term_obj.save()
lecture_obj.term.add(term_obj) lecture_obj.term.add(term_obj)
except IntegrityError as err: except IntegrityError as err:
print("ROOM_ID: " + str(room_id)) logger.exception(err)
print(err.args)
def writeUnivisLectureDataInDB(data): def writeUnivisLectureDataInDB(data):
@ -91,43 +91,56 @@ def writeUnivisLectureDataInDB(data):
lecture_type = lecture['type'] lecture_type = lecture['type']
if 'dozs' in lecture: if 'dozs' in lecture:
lecturer_id = dict(lecture['dozs']['doz']['UnivISRef'])['@key'] 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, lecture_obj = Lecture.objects.create(univis_ref=key, univis_id=univis_id, name=name, short=short,
type=lecture_type, lecturer_id=lecturer_id) type=lecture_type, lecturer_id=lecturer_id)
writeUnivisLectureTermsInDB(lecture, lecture_obj) writeUnivisLectureTermsInDB(lecture, lecture_obj)
lecture_obj.save() lecture_obj.save()
logger.info("Lecture: {}".format(lecture_obj.short))
except IntegrityError as err: except IntegrityError as err:
print(err.args) logger.warning('Lecture already exists')
# logger.exception(err)
return return
def showStatus(status: str): def showStatus(status: str):
print(status) return "\nStatus: {status}\n\tLectures: {lectures}\n\tLecture Terms: {lecture_terms}\n\tRoom: {room}".format(
pprint("Lectures: " + str(Lecture.objects.count())) status=status,
pprint("Lecture Terms: " + str(Lecture_Terms.objects.count())) lectures=Lecture.objects.count(),
pprint("Room: " + str(Room.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(): def main():
# get food jsons # get food jsons
showStatus("Start with:") logger.info(showStatus("Start SoWi:"))
writeUnivisLectureDataInDB(univis_lectures_parser.parsePage(univis_lectures(FAKULTAET_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))) 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))) 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))) writeUnivisLectureDataInDB(univis_lectures_parser.parsePage(univis_lectures(FAKULTAET_WIAI)))
pprint("----------------------------------------------------------------------------------------") # pprint("----------------------------------------------------------------------------------------")
showStatus("After WIAI:") logger.info(showStatus("Finished:"))
if __name__ == '__main__': 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_rooms_parser
from apps.donar.utils.parser import univis_lectures_parser from apps.donar.utils.parser import univis_lectures_parser
import logging
logger = logging.getLogger(__name__)
# CONFIG Fakultaet # CONFIG Fakultaet
FAKULTAET_GuK = "Fakult%E4t%20Geistes-%20und%20Kulturwissenschaften" FAKULTAET_GuK = "Fakult%E4t%20Geistes-%20und%20Kulturwissenschaften"
FAKULTAET_SoWi = "Fakult%E4t%20Sozial-%20und%20Wirtschaftswissenschaften" FAKULTAET_SoWi = "Fakult%E4t%20Sozial-%20und%20Wirtschaftswissenschaften"
@ -73,15 +77,28 @@ def writeUnivisRoomDataInDB(data):
if 'description' in room: if 'description' in room:
description = room['description'] 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) orgname=orgname, short=short, size=size, description=description)
room.save()
logger.info('ROOM: {}'.format(room.short))
except IntegrityError as err: 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(): def main():
# get food jsons # 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_GuK)))
writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms(FAKULTAET_SoWi))) writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms(FAKULTAET_SoWi)))
writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms(FAKULTAET_HuWi))) 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("d")))
writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms_loc("x"))) 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__': 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.utils.parser import fekide_eventpage_parser
from apps.events.models import Event, Location from apps.events.models import Event, Location
import logging
logger = logging.getLogger(__name__)
UNIVIS_CATEGORY = 'Univis' UNIVIS_CATEGORY = 'Univis'
@ -20,32 +23,17 @@ UNIVIS_RPG_WIAI = "http://univis.uni-bamberg.de/prg?search=events&department=Fak
def writeFekideDataInDB(data): def writeFekideDataInDB(data):
for date in data['dates']: for date in data['dates']:
for event in date['events']: for event in date['events']:
try: location, _ = Location.objects.get_or_create(name=event['location'])
Location.objects.create(name=event['location']) location.save()
except IntegrityError:
# print("Location %s already exists." % event['location'])
pass
try: event_obj, _ = Event.objects.get_or_create(date=datetime.strptime(date['date'], "%d.%m.%Y"),
event_obj, new = Event.objects.get_or_create(date=datetime.strptime(date['date'], "%d.%m.%Y"), title=event['title'])
title=event['title']) event_obj.category = event['category']
if new: event_obj.link = event['link']
event_obj.category = event['category'] event_obj.time = datetime.strptime(str(event['time']).split()[1], "%H:%M")
event_obj.link = event['link'] event_obj.locations.add(Location.objects.get(name=event['location']))
event_obj.time = datetime.strptime(str(event['time']).split()[1], "%H:%M") event_obj.save()
event_obj.locations.add(Location.objects.get(name=event['location'])) logger.info('CREATED - Event: {}'.format(event_obj.title))
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
def deleteUnivisObjects(): def deleteUnivisObjects():
@ -62,15 +50,13 @@ def writeUnivisLocationsInDB(rooms):
for room in rooms: for room in rooms:
if '@key' in room and 'short' in room: if '@key' in room and 'short' in room:
try: try:
Location.objects.create(key=room['@key'], name=room['short']) location, _ = Location.objects.get_or_create(key=room['@key'], name=room['short'])
except IntegrityError: location.key = room['@key']
print("Possible Duplicate! Start DB refresh") location.name = room['short']
try: location.save()
Location.objects.get(name=room['short']).key = room['@key'] logger.info('CREATE - Location: {}'.format(location.name))
except Exception as harderr:
print("Failed to refresh object" + harderr.args)
except Exception as err: except Exception as err:
print(err.args) logger.critical(err.args)
def getLocationIDs(event): def getLocationIDs(event):
@ -110,21 +96,24 @@ def writeUnivisEventsInDB(events: list):
event_obj.orgname = event['orgname'] event_obj.orgname = event['orgname']
try: try:
event_obj.save() event_obj.save()
logger.info(event_obj.title)
except IntegrityError: except IntegrityError:
# TODO: Update DB Object if duplicate detected # TODO: Update DB Object if duplicate detected
print("Found Duplicate!") logger.info("Found Duplicate!")
except Exception as err: except Exception as err:
print(err.args) logger.exception(err.args)
Event.objects.filter(title="").delete() Event.objects.filter(title="").delete()
def write_out_db_objects(): def write_out_db_objects():
pprint("Event: " + str(Event.objects.count())) return "\n\tEvent: {event}\n\tLocation: {location}".format(
pprint("Location: " + str(Location.objects.count())) event=Event.objects.count(),
location=Location.objects.count(),
)
def main(): def main():
print("Aktueller Stand:") logger.info("Aktueller Stand:")
write_out_db_objects() write_out_db_objects()
# deleteUnivisObjects() # deleteUnivisObjects()
@ -139,7 +128,7 @@ def main():
writeFekideDataInDB(fekide_eventpage_parser.parsePage()) writeFekideDataInDB(fekide_eventpage_parser.parsePage())
print("Neuer Stand:") logger.info("Neuer Stand:")
write_out_db_objects() write_out_db_objects()

View File

@ -2,7 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib import admin 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): class SingleFoodInline(admin.TabularInline):
@ -22,7 +22,8 @@ class MenuAdmin(admin.ModelAdmin):
# Register your models here. # Register your models here.
admin.site.register(SingleFood) admin.site.register(SingleFood)
admin.site.register(HappyHour) admin.site.register(HappyHour)
admin.site.register(UserRating) admin.site.register(UserFoodRating)
admin.site.register(UserFoodImage) admin.site.register(UserFoodImage)
admin.site.register(UserFoodComment)
admin.site.register(Menu, MenuAdmin) admin.site.register(Menu, MenuAdmin)
admin.site.register(FoodImage) 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')) 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
""" """
from django.conf.urls import url 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 from apps.food.models import Menu
urlpatterns = [ urlpatterns = [

View File

@ -4,11 +4,11 @@ from __future__ import unicode_literals
from datetime import datetime from datetime import datetime
from datetime import timedelta 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 apps.food.models import Menu, HappyHour
from rest_framework import viewsets 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 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 django.core.management.base import BaseCommand
from apps.food.models import Menu, HappyHour, SingleFood
from apps.food.utils import migrate_data 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 import os
from io import BytesIO from io import BytesIO
import uuid
from _datetime import datetime
from PIL import Image from PIL import Image
from django.conf import settings 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.core.files.uploadedfile import SimpleUploadedFile
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.core.validators import MaxValueValidator, MinValueValidator
MAX_LENGTH = 256 MAX_LENGTH = 256
MAX_FOOD_NAME = 256 MAX_FOOD_NAME = 256
@ -18,6 +21,20 @@ MAX_FOOD_PRICE_LENGTH = 10
MAX_FOOD_ALLERGENNAME_LENGTH = 256 MAX_FOOD_ALLERGENNAME_LENGTH = 256
MAX_HAPPY_HOUR_LOCATION_LENGTH = 256 MAX_HAPPY_HOUR_LOCATION_LENGTH = 256
MAX_HAPPY_HOUR_DESCRIPTION_LENGTH = 1024 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. # Create your models here.
@ -29,6 +46,13 @@ class Menu(models.Model):
LOCATION_CHOICES = ( LOCATION_CHOICES = (
(ERBA, 'Erba'), (MARKUSPLATZ, 'Markusplatz'), (FEKI, 'Feldkirchenstrasse'), (AUSTRASSE, 'Austrasse')) (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) id = models.AutoField(primary_key=True)
date = models.DateField(default=timezone.now) date = models.DateField(default=timezone.now)
location = models.CharField(max_length=MAX_FOOD_LOCATION_LENGTH, choices=LOCATION_CHOICES) 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_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_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) 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) rating = models.FloatField(default=0)
allergens = models.ManyToManyField("Allergene", blank=True) allergens = models.ManyToManyField("Allergene", blank=True)
comments = models.ManyToManyField('UserFoodComment', blank=True)
def __str__(self): def __str__(self):
return "%s - Rating: %f - Student Price: %s" % (self.name, self.rating, self.price_student) 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) date = models.DateField(default=timezone.now)
starttime = models.TimeField(default=timezone.now) starttime = models.TimeField(default=timezone.now)
endtime = 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) description = models.CharField(max_length=MAX_HAPPY_HOUR_DESCRIPTION_LENGTH)
class Meta: class Meta:
# TODO: unique description instead of date unique_together = ('location', 'starttime', 'endtime')
unique_together = ('date', 'location', 'starttime', 'endtime')
def __str__(self): def __str__(self):
return "Date: %s, Location: %s" % (self.date.strftime("%Y.%m.%d"), self.location) 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) id = models.AutoField(primary_key=True)
user = models.ForeignKey(User, on_delete=models.PROTECT, unique=False) user = models.ForeignKey(User, on_delete=models.PROTECT, unique=False)
food = models.ForeignKey(SingleFood, on_delete=models.PROTECT) 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): def __str__(self):
return "User: %s - Rating: %s" % (self.user.username, self.rating) 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)) 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): class FoodImage(models.Model):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
image = models.ImageField(upload_to='food/originals/%Y/%m/%W', blank=True, null=True) image = models.ImageField(upload_to=image_path, blank=False, null=False)
thumb = models.ImageField(upload_to='food/thumbs/%Y/%m/%W', blank=True, null=True) 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) image = Image.open(self.image)
if image.mode not in ('L', 'RGB'): if image.mode not in ('L', 'RGB'):
image = image.convert('RGB') image = image.convert('RGB')
thumb_size = (128, 128)
image.thumbnail(thumb_size, Image.ANTIALIAS) image.thumbnail(thumb_size, Image.ANTIALIAS)
# save the thumbnail to memory # save the thumbnail to memory
@ -124,12 +174,17 @@ class FoodImage(models.Model):
temp_handle.read(), temp_handle.read(),
content_type='image/jpg') content_type='image/jpg')
self.thumb.save('%s_thumbnail.%s' % (self.id, 'jpg'), suf, save=False) # self.thumb.save('%s_thumbnail.%s' % (self.id, 'jpg'), suf, save=False)
# save the image object self.thumb.save(name='', content=suf, save=False)
self.image.name = "%s_original.%s" % (self.id, 'jpg')
super(FoodImage, self).save(force_update, force_insert)
def delete(self, using=None, keep_parents=False): # save the image object
os.remove(os.path.join(settings.MEDIA_ROOT, self.image.name)) super(FoodImage, self).save(*args, **kwargs)
os.remove(os.path.join(settings.MEDIA_ROOT, self.thumb.name))
super(FoodImage, self).delete() #
# 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.test import TestCase
from django.urls import reverse from django.urls import reverse
from apps.food.models import SingleFood, Menu 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 import status
from rest_framework.test import APIRequestFactory
from datetime import datetime 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 1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 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 rest_framework import routers
from apps.food import views, admin_views from apps.food import views, admin_views
from django.conf.urls import url, include 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
# API Version 1.0 # API Version 1.0
apiRouter_v1 = routers.DefaultRouter() apiRouter_v1 = routers.DefaultRouter()

View File

@ -2,8 +2,11 @@ import json
from datetime import datetime from datetime import datetime
from pprint import pprint from pprint import pprint
from django.db.utils import IntegrityError 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 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 # 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" 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') 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): def getLocation(raw_loc):
for choice, name in zip(Menu.LOCATION_CHOICES, LOCATION_NAMES): 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 return choice
logger.warning("{loc} unknown location".format(loc=raw_loc))
print("LOCATION NOT FOUND") return None
def writeStudentenwerkDataInDB(data): def writeStudentenwerkDataInDB(data):
data = json.loads(data) if not data:
pprint(data) logger.warning('no data')
return
logger.info("{location}".format(location=data['name']))
for menu in data['weekmenu']: for menu in data['weekmenu']:
pprint(menu) logger.info("{date}".format(date=menu['date']))
foodlist = [] foodlist = []
for single_food in menu['menu']: for single_food in menu['menu']:
pprint(single_food) logger.info("{}".format(single_food['title']))
allergens = []
if 'allergens' in single_food: if 'allergens' in single_food:
allergens = []
for allergen in single_food['allergens']: for allergen in single_food['allergens']:
try: allergens.append(Allergene.objects.get_or_create(name=allergen)[0])
allergens.append(Allergene.objects.create(name=allergen)) # TODO: Consider keyword arg for price
except IntegrityError:
allergens.append(Allergene.objects.get(name=allergen))
try: try:
if 'prices' in single_food: db_single_food, created = SingleFood.objects.get_or_create(name=single_food['title'])
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'])
if 'prices' in single_food: if 'prices' in single_food:
if 'price_student' in single_food['prices']: if 'price_student' in single_food['prices']:
db_single_food.price_student = single_food['prices']['price_student'] db_single_food.price_student = single_food['prices']['price_student']
else:
db_single_food.price_student = "None"
if 'price_employee' in single_food['prices']: if 'price_employee' in single_food['prices']:
db_single_food.price_employee = single_food['prices']['price_employee'] db_single_food.price_employee = single_food['prices']['price_employee']
else:
db_single_food.price_employee = "None"
if 'price_guest' in single_food['prices']: if 'price_guest' in single_food['prices']:
db_single_food.price_guest = single_food['prices']['price_guest'] 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) db_single_food.allergens.set(allergens)
foodlist.append(db_single_food) 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) 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.menu.set(foodlist)
menu.save() menu.save()
except IntegrityError as error: except IntegrityError as error:
# ignored logger.exception(error)
pass
def writeFekideDataInDB(data): def writeFekideDataInDB(data):
for happyhour_data in data['happyhours']: for happyhour_data in data['happyhours']:
time = str(happyhour_data['time']).replace(" ", "").split("-") time = str(happyhour_data['time']).replace(" ", "").split("-")
happyhour, new = HappyHour.objects.get_or_create(date=datetime.strptime(data['day'], "%A, %d.%m.%Y"), try:
location=happyhour_data['location'], location, _ = HappyHourLocation.objects.get_or_create(name=happyhour_data['location'])
description=happyhour_data['description'], happyhour, _ = HappyHour.objects.get_or_create(location=location,
starttime=datetime.strptime(time[0], "%H:%M").time(), starttime=datetime.strptime(time[0], "%H:%M").time(),
endtime=datetime.strptime(time[1], "%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.date = datetime.strptime(data['day'], "%A, %d.%m.%Y")
happyhour.location = happyhour_data['location']
happyhour.description = happyhour_data['description'] 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() happyhour.save()
print("%s: Happy Hour: Location: %s, Description: %s" % ( logger.info("{date}: Happy Hour: Location: {location}, Description: {description}".format(
str(happyhour.date.date()), str(happyhour.location), str(happyhour.description))) date=happyhour.date,
location=happyhour.location,
description=happyhour.description)
)
except Exception as e:
logger.exception(e)
def writeoutDBObjects(): def writeoutDBObjects():
pprint("SingleFood: " + str(SingleFood.objects.count())) return "\n\tSingleFood: {single_food}\n\tMenu: {menu}\n\tHappyHour: {happy_hour}".format(
pprint("Menu: " + str(Menu.objects.count())) single_food=SingleFood.objects.count(),
pprint("HappyHour: " + str(HappyHour.objects.count())) menu=Menu.objects.count(),
happy_hour=HappyHour.objects.count()
)
def delete(): def delete():
happy_hours = HappyHour.objects.all() happy_hours = HappyHour.objects.all()
print("Deleted following Happy Hours:") logger.info("Deleted following Happy Hours:")
for happy_hour in happy_hours: for happy_hour in happy_hours:
print("%s: Happy Hour: Location: %s, Description: %s" % ( logger.info("{date}: Happy Hour: Location: {location}, Description: {description}".format(
str(happy_hour.date), str(happy_hour.location), str(happy_hour.description))) date=happy_hour.date,
location=happy_hour.location,
description=happy_hour.description)
)
happy_hour.delete() happy_hour.delete()
def main(): def main():
print("Aktueller Stand:") logger.info("Aktueller Stand:" + writeoutDBObjects())
writeoutDBObjects()
# get food jsons # get food jsons
writeStudentenwerkDataInDB(mensa_page_parser.parsePage(LINK_AUSTR_MENSA)) writeStudentenwerkDataInDB(mensa_page_parser.parsePage(LINK_AUSTR_MENSA))
writeStudentenwerkDataInDB(mensa_page_parser.parsePage(LINK_FEKI_MENSA)) writeStudentenwerkDataInDB(mensa_page_parser.parsePage(LINK_FEKI_MENSA))
writeStudentenwerkDataInDB(cafete_page_parser.parsePage(LINK_ERBA_CAFETE)) writeStudentenwerkDataInDB(cafete_page_parser.parse_page(LINK_ERBA_CAFETE))
writeStudentenwerkDataInDB(cafete_page_parser.parsePage(LINK_MARKUS_CAFETE)) writeStudentenwerkDataInDB(cafete_page_parser.parse_page(LINK_MARKUS_CAFETE))
writeFekideDataInDB(fekide_happyhour_page_parser.parsePage(LINK_FEKIDE_GUIDE)) writeFekideDataInDB(fekide_happyhour_page_parser.parse_page(LINK_FEKIDE_GUIDE))
print("Neuer Stand:") logger.info("Neuer Stand:" + writeoutDBObjects())
writeoutDBObjects()
if __name__ == '__main__': 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 datetime
import logging
import re 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' SPEISEPLAN_NAME_SELECTOR = '.csc-default .csc-header .csc-firstHeader'
def loadPage(url: str): def get_foodplan_name(soup):
return requests.get(url).content
def getFoodplanName(soup):
foodplan_name = soup.select(SPEISEPLAN_NAME_SELECTOR)[0].getText() foodplan_name = soup.select(SPEISEPLAN_NAME_SELECTOR)[0].getText()
return foodplan_name return foodplan_name
def getRightLine(lines): def get_right_line(lines):
foodlines = [] foodlines = []
pattern = re.compile("[0-9]+.+[A-Z]+") pattern = re.compile("[0-9]+.+[A-Z]+")
for line in list(lines): for line in list(lines):
@ -27,42 +26,42 @@ def getRightLine(lines):
return foodlines return foodlines
def getFoodPerDay(soup): def get_food_per_day(soup):
days = [] days = []
lines = soup.select('.csc-default .bodytext') lines = soup.select('.csc-default .bodytext')
foodlines = getRightLine(lines) foodlines = get_right_line(lines)
for food in foodlines: for food in foodlines:
dayObj = {}
day = str(food).split()[0] day = str(food).split()[0]
foodName = str(food).replace(day, "").strip() food_name = str(food).replace(day, "").strip()
singleFoodObj = {} single_food_obj = {'title': food_name}
singleFoodObj['title'] = foodName day_obj = {
dayObj['date'] = day 'date': day,
dayObj['menu'] = [singleFoodObj] 'menu': [single_food_obj]
days.append(dayObj) }
days.append(day_obj)
return days return days
def parsePage(url: str): def parse_page(url: str):
pagecontent = {} pagecontent = {}
# {mensaspeiseplan: # {mensaspeiseplan:
# {name:"", # {name:"",
# weekmenu: [day:{date:, menu:[,,,]}] # weekmenu: [day:{date:, menu:[,,,]}]
# } # }
# } # }
try:
page = load_page(url)
soup = BeautifulSoup(page, "lxml")
foodplan_name = get_foodplan_name(soup)
page = loadPage(url) days = get_food_per_day(soup)
mensaSpeiseplan = {} return {
soup = BeautifulSoup(page, "lxml") 'weekmenu': days,
foodplan_name = getFoodplanName(soup) 'name': foodplan_name,
'execution_time': datetime.datetime.today().strftime("%A, %d.%m.%Y")
days = getFoodPerDay(soup) }
mensaSpeiseplan['weekmenu'] = days except Exception as e:
mensaSpeiseplan['name'] = foodplan_name logger.exception(e)
mensaSpeiseplan['execution_time'] = datetime.datetime.today().strftime("%A, %d.%m.%Y") return None
mensaSpeiseplanJson = json.dumps(mensaSpeiseplan)
return mensaSpeiseplanJson
# LINK_ERBA_CAFETE = "https://www.studentenwerk-wuerzburg.de/bamberg/essen-trinken/sonderspeiseplaene/cafeteria-erba-insel.html" # 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 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' SPEISEPLAN_NAME_SELECTOR = '.csc-default .csc-header .csc-firstHeader'
def loadPage(url: str): def get_day():
return requests.get(url).content
def getDay():
return datetime.datetime.today().strftime("%A, %d.%m.%Y") return datetime.datetime.today().strftime("%A, %d.%m.%Y")
def getHappyHours(soup): def get_happy_hours(soup):
happyhours = [] happyhours = []
happyhourstable = soup.select('#food .table tr') happyhourstable = soup.select('#food .table tr')
for tableline in happyhourstable: for tableline in happyhourstable:
happyhour = {}
linesoup = BeautifulSoup(str(tableline), "lxml") linesoup = BeautifulSoup(str(tableline), "lxml")
location = linesoup.find("td", {"class": "location"}).getText() location = linesoup.find("td", {"class": "location"}).getText()
time = linesoup.find("td", {"class": "time"}).getText() time = linesoup.find("td", {"class": "time"}).getText()
description = linesoup.find("td", {"class": "description"}).getText() description = linesoup.find("td", {"class": "description"}).getText()
description = str(description).strip() description = str(description).strip()
happyhour = {
happyhour['location'] = location 'location': location,
happyhour['time'] = time 'time': time,
happyhour['description'] = description 'description': description
}
happyhours.append(happyhour) happyhours.append(happyhour)
return happyhours return happyhours
def parsePage(url: str): def parse_page(url: str):
pagecontent = {}
# { # {
# happyhours:[{happyhour:{location: "",time: "",description: ""},,,,] # happyhours:[{happyhour:{location: "",time: "",description: ""},,,,]
# } # }
happyhours = [] happyhours = []
try:
page = loadPage(url) page = load_page(url)
soup = BeautifulSoup(page, "lxml") soup = BeautifulSoup(page, "lxml")
happyhours = getHappyHours(soup) happyhours = get_happy_hours(soup)
pagecontent['happyhours'] = happyhours return {
pagecontent['day'] = getDay() 'happyhours': happyhours,
pagecontent['execution_time'] = datetime.datetime.today().strftime("%A, %d.%m.%Y") 'day': get_day(),
return pagecontent '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" # LINK_FEKIDE_GUIDE = "https://www.feki.de/happyhour/wochenuebersicht"
# parsePage(LINK_FEKIDE_GUIDE) # parsePage(LINK_FEKIDE_GUIDE)

View File

@ -1,14 +1,11 @@
import requests
from bs4 import BeautifulSoup
import json
import datetime 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
logger = logging.getLogger(__name__)
def loadPage(url: str):
return requests.get(url).content
def getMenuDay(soup): def getMenuDay(soup):
@ -18,12 +15,10 @@ def getMenuDay(soup):
def getFoodPerDay(soup): def getFoodPerDay(soup):
week_menus = [] week_menus = []
for day in soup.select('.currentweek .day'): for day in soup.select('.currentweek .day'):
menu = {}
daysoup = BeautifulSoup(str(day), "lxml") daysoup = BeautifulSoup(str(day), "lxml")
day = getMenuDay(daysoup) day = getMenuDay(daysoup)
day_menu = [] day_menu = []
for singleFood in daysoup.select('.menuwrap .menu'): for singleFood in daysoup.select('.menuwrap .menu'):
singleFoodObj = {}
singleFoodSoup = BeautifulSoup(str(singleFood), "lxml") singleFoodSoup = BeautifulSoup(str(singleFood), "lxml")
title = singleFoodSoup.find('div', {'class': 'title'}).getText() title = singleFoodSoup.find('div', {'class': 'title'}).getText()
allergens = [e.getText() for e in singleFoodSoup.select('.left .additnr .toggler ul li')] 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'] prices['price_employee'] = singleFoodSoup.select('.price')[0]['data-bed']
if singleFoodSoup.select('.price'): if singleFoodSoup.select('.price'):
prices['price_guest'] = singleFoodSoup.select('.price')[0]['data-guest'] prices['price_guest'] = singleFoodSoup.select('.price')[0]['data-guest']
singleFoodObj['title'] = title single_food_obj = {
singleFoodObj['allergens'] = allergens 'title': title,
singleFoodObj['prices'] = prices 'allergens': allergens,
day_menu.append(singleFoodObj) 'prices': prices
}
menu['date'] = str(day).split(" ")[1] day_menu.append(single_food_obj)
menu['menu'] = day_menu menu = {
'date': str(day).split(" ")[1],
'menu': day_menu
}
week_menus.append(menu) week_menus.append(menu)
return week_menus return week_menus
@ -52,16 +50,19 @@ def parsePage(url: str):
# weekmenu: [day:{date:, menu:[,,,]}] # weekmenu: [day:{date:, menu:[,,,]}]
# } # }
# } # }
mensaSpeiseplan = {} try:
page = loadPage(url) page = load_page(url)
soup = BeautifulSoup(page, "lxml") soup = BeautifulSoup(page, "lxml")
foodplan_name = getFoodplanName(soup) foodplan_name = getFoodplanName(soup)
days = getFoodPerDay(soup) days = getFoodPerDay(soup)
mensaSpeiseplan['weekmenu'] = days return {
mensaSpeiseplan['name'] = foodplan_name 'weekmenu': days,
mensaSpeiseplan['execution_time'] = datetime.datetime.today().strftime("%A, %d.%m.%Y") 'name': foodplan_name,
mensaSpeiseplanJson = json.dumps(mensaSpeiseplan) 'execution_time': datetime.datetime.today().strftime("%A, %d.%m.%Y")
return mensaSpeiseplanJson }
except Exception as e:
logger.exception(e)
return None
def getFoodplanName(soup): def getFoodplanName(soup):

View File

@ -7,7 +7,7 @@ from django.http import HttpResponse
from django.shortcuts import render from django.shortcuts import render
from apps.food.forms import UploadImageForm 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. # Create your views here.
@ -100,12 +100,12 @@ def food_rating(request):
rating = request.GET.get('rating', None) rating = request.GET.get('rating', None)
if food_id and rating: if food_id and rating:
food = SingleFood.objects.get(id=food_id) 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) food=food)
user_rating.rating = rating user_rating.rating = rating
user_rating.save() user_rating.save()
food_user_ratings = UserRating.objects.all().filter(food=food) food_user_ratings = UserFoodRating.objects.all().filter(food=food)
sum = 0 sum = 0
for food_user_rating in food_user_ratings: for food_user_rating in food_user_ratings:
sum += food_user_rating.rating 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'^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})/$', 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'), 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.utils.http import urlsafe_base64_decode
from django.core.mail import send_mail from django.core.mail import send_mail
from django.shortcuts import HttpResponse, redirect from django.shortcuts import HttpResponse, redirect
from apps.food.models import UserRating, UserFoodImage from apps.food.models import UserFoodRating, UserFoodImage
def signup(request): def signup(request):
@ -22,7 +22,7 @@ def signup(request):
user.is_active = False user.is_active = False
user.save() user.save()
current_site = request.META['HTTP_HOST'] 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', { message = render_to_string('registration/account_activation_email.jinja', {
'user': user, 'user': user,
'domain': current_site, 'domain': current_site,
@ -64,7 +64,7 @@ def account_view(request):
if request.user.is_authenticated: if request.user.is_authenticated:
user = request.user 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) food_images = UserFoodImage.objects.filter(user=user.id)
print(food_images) print(food_images)

View File

@ -11,132 +11,48 @@ https://docs.djangoproject.com/en/1.11/ref/settings/
""" """
import os import os
import datetime
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) DOMAIN = os.environ['DOMAIN']
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',
]
SITE_NAME = os.environ['SITE_NAME']
SITE_ID = 1 SITE_ID = 1
REST_FRAMEWORK = { ADMINS = os.environ['ADMINS'].split()
'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',
]
ROOT_URLCONF = 'core.urls' BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
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',
],
},
},
]
WSGI_APPLICATION = 'core.wsgi.application' SECRET_KEY = os.environ['SECRET_KEY']
# Database DEBUG = bool(os.environ.get('DEBUG', False))
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = { ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split()
'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 # Sign Up E-Mail authentication
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 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 = [ # TODO: more account with same email are possible?
{ ACCOUNT_EMAIL_UNIQUE = True
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', ACCOUNT_EMAIL_CONFIRMATION_REQUIRED = True
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
MIDDLEWARE_CLASSES = ( # Setup support for proxy headers
'django.middleware.security.SecurityMiddleware', USE_X_FORWARDED_HOST = True
'django.contrib.sessions.middleware.SessionMiddleware', SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # Media files should be stored here
'django.contrib.auth.middleware.AuthenticationMiddleware', MEDIA_ROOT = os.path.join(BASE_DIR, "media")
'django.contrib.auth.middleware.SessionAuthenticationMiddleware', MEDIA_URL = '/media/'
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', # monitoring
) PIWIK_DOMAIN_PATH = os.environ['PIWIK_DOMAIN_PATH']
PIWIK_SITE_ID = os.environ['PIWIK_SITE_ID']
LOGIN_REDIRECT_URL = 'home'
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/ # 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" DATETIME_FORMAT = "l, d. F Y"
TIME_FORMAT = "H:i" TIME_FORMAT = "H:i"
# Static files (CSS, JavaScript, Images) ROOT_URLCONF = 'core.urls'
# https://docs.djangoproject.com/en/1.11/howto/static-files/ WSGI_APPLICATION = 'core.wsgi.application'
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static_files") STATIC_ROOT = os.path.join(BASE_DIR, "static_files")
print(STATIC_ROOT)
STATICFILES_DIRS = [ STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"), os.path.join(BASE_DIR, "static"),
] ]
# Setup support for proxy headers # CORS
USE_X_FORWARDED_HOST = True CORS_ORIGIN_ALLOW_ALL = False
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# FORCE_SCRIPT_NAME = "app" CORS_ORIGIN_WHITELIST = (
# Media files should be stored here 'localhost:3000',
MEDIA_ROOT = os.path.join(BASE_DIR, "media") )
MEDIA_URL = '/media/'
# monitoring # Application definition
PIWIK_DOMAIN_PATH = 'mg-server.ddns.net/piwik' INSTALLED_APPS = [
PIWIK_SITE_ID = '1' '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 MIDDLEWARE_CLASSES = (
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 'django.middleware.security.SecurityMiddleware',
EMAIL_HOST = 'smtp.gmail.com' 'django.contrib.sessions.middleware.SessionMiddleware',
EMAIL_HOST_USER = 'signup.basta@gmail.com' 'django.middleware.common.CommonMiddleware',
EMAIL_HOST_PASSWORD = '1\SL^QzlSuP<`8gkP4Fd' 'django.middleware.csrf.CsrfViewMiddleware',
EMAIL_PORT = '587' 'django.contrib.auth.middleware.AuthenticationMiddleware',
EMAIL_USE_TLS = True 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ACCOUNT_EMAIL_UNIQUE = True TEMPLATES = [
ACCOUNT_EMAIL_CONFIRMATION_REQUIRED = True {
'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 1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
""" """
from core import views from core import views
from django.conf.urls import url, include from django.conf.urls import url, include
from django.contrib import admin from django.contrib import admin
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from rest_framework import routers from rest_framework import routers
from rest_framework.authtoken import views as token_auth_views
from apps.food import urls as food_urls from apps.food import urls as food_urls
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
@ -46,7 +48,18 @@ urlpatterns = [
url(r'^impressum/$', views.impressum, name='impressum'), url(r'^impressum/$', views.impressum, name='impressum'),
# -- API -- # -- API --
# -- Version 1.0
url(r'^api/v1/', include(api_router_v1.urls)), url(r'^api/v1/', include(api_router_v1.urls)),
url(r'^api/v1.1/', include('apps.food.api.urls')), # -- Version 1.1
url(r'^api/auth/', include('rest_framework.urls', namespace='rest_framework')) 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) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -1,10 +1,14 @@
django==2.0.1 django==2.0.1
django-jinja==2.4.1 django-jinja==2.4.1
django-rest-framework==0.1.0 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 django-analytical==2.4.0
requests==2.18.4 requests==2.18.4
beautifulsoup4==4.6.0 beautifulsoup4==4.6.0
#psycopg2==2.7.3.2 #psycopg2==2.7.3.2
xmltodict==0.11.0 xmltodict==0.11.0
coverage==3.6 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() { function rate_init() {
add_Stars(); add_Stars();
$('.star').on("mouseenter mouseleave", function () { $('.star').on("mouseenter", function () {
showRating(this); showRating(this);
}).on("click", function () { }).on("click", function () {
console.log('Click'); console.log('Click');
sendRating(this); 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/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('libs/bootstrap-4.0.0-beta-dist/css/bootstrap.css') }}">
<link rel="stylesheet" href="{{ static('css/nav.css') }}"> <link rel="stylesheet" href="{{ static('css/nav.css') }}">
<link rel="stylesheet" href="{{ static('css/main.css') }}">
{% block css_extra %}{% endblock %} {% block css_extra %}{% endblock %}
<!-- Piwik --> <!-- Piwik -->
<script type="text/javascript"> <script type="text/javascript">
@ -95,7 +96,7 @@
</div> </div>
<div class="row text-center bg-dark text-white pb-2"> <div class="row text-center bg-dark text-white pb-2">
<div class="col"> <div class="col">
© Copyright 2017, Michael Götz © Copyright 2018, Michael Götz
</div> </div>
</div> </div>
</footer> </footer>

View File

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

View File

@ -9,10 +9,15 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="row text-dark"> <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-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-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>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -59,7 +59,6 @@
{% for single_food in menu %} {% for single_food in menu %}
<li data-food="{{ single_food.id }}" data-rating="{{ single_food.rating }}" class="food-item media mb-2"> <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"> <div class="mr-2 media-left media-middle">
{# TODO: without many to many #}
{% if single_food.image %} {% if single_food.image %}
<a href="{{ single_food.image.image.url }}" data-lightbox="{{ title }}" <a href="{{ single_food.image.image.url }}" data-lightbox="{{ title }}"
data-title="{{ single_food.name }}"> data-title="{{ single_food.name }}">

View File

@ -11,11 +11,11 @@
</div> </div>
{% endmacro %} {% 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="col-12 col-sm-6 col-lg-6 col-xl-6 p-3">
<div class="card"> <div class="card">
{% if url_id %} {% if url_id %}
<a href="{{ url(url_id) }}"> <a href="{{ url(url_id) }}" {{ attr }}>
<div class="card-body"> <div class="card-body">
<h4 class="card-title text-center"><i class="fa {{ icon }} {{ icon_size }}" <h4 class="card-title text-center"><i class="fa {{ icon }} {{ icon_size }}"
aria-hidden="true"></i></h4> aria-hidden="true"></i></h4>
@ -25,7 +25,7 @@
</div> </div>
</a> </a>
{% else %} {% else %}
<a href="{{ link }}"> <a href="{{ link }}" {{ attr }}>
<div class="card-body"> <div class="card-body">
<h4 class="card-title text-center"><i class="fa {{ icon }} {{ icon_size }}" <h4 class="card-title text-center"><i class="fa {{ icon }} {{ icon_size }}"
aria-hidden="true"></i></h4> aria-hidden="true"></i></h4>

View File

@ -8,15 +8,22 @@
<label for="username" class="label">Username:</label> <label for="username" class="label">Username:</label>
<p> <p>
<input id="username" type="text" name="username" required autofocus maxlength="150" id="id_username"/> <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> </small>
</p> </p>
<label for="email" class="label">E-Mail:</label> <label for="email" class="label">E-Mail:</label>
<p> <p>
<input id="email" type="email" name="email" required maxlength="254" id="id_email"/> <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> </p>
<label for="password1" class="label">Password:</label> <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