diff --git a/Dockerfile.dev b/Dockerfile.dev index 9ef941f..5717839 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -3,9 +3,11 @@ ADD ["ofu_app/requirements.txt", "/requirements.txt"] RUN apk upgrade --update && \ apk add --update python3 py3-pillow py3-lxml py3-psycopg2 && \ pip3 install -r /requirements.txt && rm /requirements.txt +EXPOSE 80 WORKDIR /app VOLUME ["/app"] VOLUME ["/app/data"] VOLUME ["/app/media"] +VOLUME ["/app/log"] ENTRYPOINT ["python3", "manage.py"] CMD ["runserver", "0.0.0.0:80"] diff --git a/docker-compose.yml b/docker-compose.yml index a2d7447..51697aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: volumes: - ./data/data:/data - ./data/media:/media + - ./log:/log - ./ofu_app/:/app env_file: - docker.env diff --git a/ofu_app/apps/donar/management/commands/delete_lectures.py b/ofu_app/apps/donar/management/commands/delete_lectures.py new file mode 100644 index 0000000..444ff9a --- /dev/null +++ b/ofu_app/apps/donar/management/commands/delete_lectures.py @@ -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() diff --git a/ofu_app/apps/donar/management/commands/delete_rooms.py b/ofu_app/apps/donar/management/commands/delete_rooms.py new file mode 100644 index 0000000..b26763a --- /dev/null +++ b/ofu_app/apps/donar/management/commands/delete_rooms.py @@ -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() diff --git a/ofu_app/apps/donar/migrations/0005_auto_20180401_1136.py b/ofu_app/apps/donar/migrations/0005_auto_20180401_1136.py new file mode 100644 index 0000000..5d72ed0 --- /dev/null +++ b/ofu_app/apps/donar/migrations/0005_auto_20180401_1136.py @@ -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), + ), + ] diff --git a/ofu_app/apps/donar/migrations/0006_auto_20180401_1139.py b/ofu_app/apps/donar/migrations/0006_auto_20180401_1139.py new file mode 100644 index 0000000..38385a7 --- /dev/null +++ b/ofu_app/apps/donar/migrations/0006_auto_20180401_1139.py @@ -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), + ), + ] diff --git a/ofu_app/apps/donar/migrations/0007_auto_20180401_1140.py b/ofu_app/apps/donar/migrations/0007_auto_20180401_1140.py new file mode 100644 index 0000000..f8f72c7 --- /dev/null +++ b/ofu_app/apps/donar/migrations/0007_auto_20180401_1140.py @@ -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), + ), + ] diff --git a/ofu_app/apps/donar/migrations/0008_auto_20180401_1142.py b/ofu_app/apps/donar/migrations/0008_auto_20180401_1142.py new file mode 100644 index 0000000..f19cd75 --- /dev/null +++ b/ofu_app/apps/donar/migrations/0008_auto_20180401_1142.py @@ -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), + ), + ] diff --git a/ofu_app/apps/donar/migrations/0009_auto_20180401_1143.py b/ofu_app/apps/donar/migrations/0009_auto_20180401_1143.py new file mode 100644 index 0000000..b65e3cd --- /dev/null +++ b/ofu_app/apps/donar/migrations/0009_auto_20180401_1143.py @@ -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), + ), + ] diff --git a/ofu_app/apps/donar/models.py b/ofu_app/apps/donar/models.py index 6450cc5..484e5da 100644 --- a/ofu_app/apps/donar/models.py +++ b/ofu_app/apps/donar/models.py @@ -11,8 +11,8 @@ MAX_COORDS_NAME_LENGTH = 256 MAX_COORDS_LENGTH = 256 MAX_LECTURE_IDS_LENGTH = 256 -MAX_LECTURE_SHORT_LENGTH = 128 -MAX_LECTURE_NAME_LENGTH = 256 +MAX_LECTURE_SHORT_LENGTH = 512 +MAX_LECTURE_NAME_LENGTH = 512 MAX_LECTURE_TYPE_LENGTH = 64 diff --git a/ofu_app/apps/donar/utils/migrate_data_lectures.py b/ofu_app/apps/donar/utils/migrate_data_lectures.py index 6b85e57..15fce8e 100644 --- a/ofu_app/apps/donar/utils/migrate_data_lectures.py +++ b/ofu_app/apps/donar/utils/migrate_data_lectures.py @@ -4,6 +4,9 @@ import json from pprint import pprint from django.db.utils import IntegrityError from apps.donar.utils.parser import univis_lectures_parser +import logging + +logger = logging.getLogger(__name__) # CONFIG Fakultaet FAKULTAET_GuK = "Fakult%E4t%20Geistes-%20und%20Kulturwissenschaften" @@ -32,38 +35,35 @@ def writeUnivisLectureTermsInDB(lecture, lecture_obj): if type(lecture['terms']['term']) == list: for term in lecture['terms']['term']: try: - term_obj = Lecture_Terms.objects.create() starttime = "00:00" + term_obj = Lecture_Terms.objects.create(starttime=starttime) if 'starttime' in term: starttime = term['starttime'] term_obj.starttime = datetime.strptime(starttime, "%H:%M") + term_obj.save() if 'room' in term: room_id = term['room']['UnivISRef']['@key'] - term_obj.room = [Room.objects.get(key=room_id)] - term_obj.save() + term_obj.room.add(Room.objects.get(key=room_id)) lecture_obj.term.add(term_obj) except IntegrityError as err: - print("ROOM_ID: " + str(room_id)) - print(err.args) + logger.exception(err) else: try: - term_obj = Lecture_Terms.objects.create() univis_starttime = "00:00" + term_obj = Lecture_Terms.objects.create(starttime=univis_starttime) if 'starttime' in lecture['terms']['term']: univis_starttime = lecture['terms']['term']['starttime'] term_obj.starttime = datetime.strptime(univis_starttime, '%H:%M') + term_obj.save() if 'room' in lecture['terms']['term']: room_id = lecture['terms']['term']['room']['UnivISRef']['@key'] - pprint("Room: " + room_id) Room.objects.get(key=room_id) - term_obj.room = [Room.objects.get(key=room_id)] - + term_obj.room.add(Room.objects.get(key=room_id)) term_obj.save() lecture_obj.term.add(term_obj) except IntegrityError as err: - print("ROOM_ID: " + str(room_id)) - print(err.args) + logger.exception(err) def writeUnivisLectureDataInDB(data): @@ -91,43 +91,56 @@ def writeUnivisLectureDataInDB(data): lecture_type = lecture['type'] if 'dozs' in lecture: lecturer_id = dict(lecture['dozs']['doz']['UnivISRef'])['@key'] - print("Lecture: " + name) lecture_obj = Lecture.objects.create(univis_ref=key, univis_id=univis_id, name=name, short=short, type=lecture_type, lecturer_id=lecturer_id) writeUnivisLectureTermsInDB(lecture, lecture_obj) lecture_obj.save() + logger.info("Lecture: {}".format(lecture_obj.short)) except IntegrityError as err: - print(err.args) + logger.warning('Lecture already exists') + # logger.exception(err) return def showStatus(status: str): - print(status) - pprint("Lectures: " + str(Lecture.objects.count())) - pprint("Lecture Terms: " + str(Lecture_Terms.objects.count())) - pprint("Room: " + str(Room.objects.count())) + return "\nStatus: {status}\n\tLectures: {lectures}\n\tLecture Terms: {lecture_terms}\n\tRoom: {room}".format( + status=status, + lectures=Lecture.objects.count(), + lecture_terms=Lecture_Terms.objects.count(), + room=Room.objects.count() + ) + + +def delete(): + lectures = Lecture.objects.all() + logger.info("Deleted following Lectures:") + for lecture in lectures: + logger.info("Lecture: {name}".format( + name=lecture.name) + ) + lecture.delete() def main(): # get food jsons - showStatus("Start with:") + logger.info(showStatus("Start SoWi:")) writeUnivisLectureDataInDB(univis_lectures_parser.parsePage(univis_lectures(FAKULTAET_SoWi))) - pprint("----------------------------------------------------------------------------------------") + # pprint("----------------------------------------------------------------------------------------") - showStatus("After SoWi:") + logger.info(showStatus("Start GuK:")) writeUnivisLectureDataInDB(univis_lectures_parser.parsePage(univis_lectures(FAKULTAET_GuK))) - pprint("----------------------------------------------------------------------------------------") + # pprint("----------------------------------------------------------------------------------------") - showStatus("After GuK:") + logger.info(showStatus("Start HuWi:")) writeUnivisLectureDataInDB(univis_lectures_parser.parsePage(univis_lectures(FAKULTAET_HuWi))) - pprint("----------------------------------------------------------------------------------------") + # pprint("----------------------------------------------------------------------------------------") - showStatus("After HuWi:") + logger.info(showStatus("Start WIAI:")) writeUnivisLectureDataInDB(univis_lectures_parser.parsePage(univis_lectures(FAKULTAET_WIAI))) - pprint("----------------------------------------------------------------------------------------") - - showStatus("After WIAI:") + # pprint("----------------------------------------------------------------------------------------") + + logger.info(showStatus("Finished:")) if __name__ == '__main__': diff --git a/ofu_app/apps/donar/utils/migrate_data_rooms.py b/ofu_app/apps/donar/utils/migrate_data_rooms.py index 7957c82..f5ba439 100644 --- a/ofu_app/apps/donar/utils/migrate_data_rooms.py +++ b/ofu_app/apps/donar/utils/migrate_data_rooms.py @@ -6,6 +6,10 @@ from apps.donar.models import Room, Lecture_Terms, Lecture from apps.donar.utils.parser import univis_rooms_parser from apps.donar.utils.parser import univis_lectures_parser +import logging + +logger = logging.getLogger(__name__) + # CONFIG Fakultaet FAKULTAET_GuK = "Fakult%E4t%20Geistes-%20und%20Kulturwissenschaften" FAKULTAET_SoWi = "Fakult%E4t%20Sozial-%20und%20Wirtschaftswissenschaften" @@ -73,15 +77,28 @@ def writeUnivisRoomDataInDB(data): if 'description' in room: description = room['description'] - Room.objects.create(key=key, address=address, building_key=building_key, floor=floor, name=name, - orgname=orgname, short=short, size=size, description=description) + room = Room.objects.create(key=key, address=address, building_key=building_key, floor=floor, name=name, + orgname=orgname, short=short, size=size, description=description) + room.save() + logger.info('ROOM: {}'.format(room.short)) except IntegrityError as err: - pprint(err.args) + logger.warning('Room already exists') + + +def delete(): + rooms = Room.objects.all() + logger.info("Deleted following Rooms:") + for room in rooms: + logger.info("Room: {name}".format( + name=room.short) + ) + room.delete() def main(): # get food jsons - pprint("Begin: Room: " + str(Room.objects.count())) + logger.info("Start:\nRoom: {}".format(Room.objects.count())) + writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms(FAKULTAET_GuK))) writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms(FAKULTAET_SoWi))) writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms(FAKULTAET_HuWi))) @@ -102,7 +119,7 @@ def main(): writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms_loc("d"))) writeUnivisRoomDataInDB(univis_rooms_parser.parsePage(univis_rooms_loc("x"))) - pprint("Now: Room: " + str(Room.objects.count())) + logger.info("Finished:\nRoom: {}".format(Room.objects.count())) if __name__ == '__main__': diff --git a/ofu_app/apps/events/utils/migrate_data.py b/ofu_app/apps/events/utils/migrate_data.py index b1f462a..685816c 100644 --- a/ofu_app/apps/events/utils/migrate_data.py +++ b/ofu_app/apps/events/utils/migrate_data.py @@ -7,6 +7,9 @@ from apps.events.utils.parser import univis_eventpage_parser from apps.events.utils.parser import fekide_eventpage_parser from apps.events.models import Event, Location +import logging + +logger = logging.getLogger(__name__) UNIVIS_CATEGORY = 'Univis' @@ -20,32 +23,17 @@ UNIVIS_RPG_WIAI = "http://univis.uni-bamberg.de/prg?search=events&department=Fak def writeFekideDataInDB(data): for date in data['dates']: for event in date['events']: - try: - Location.objects.create(name=event['location']) - except IntegrityError: - # print("Location %s already exists." % event['location']) - pass + location, _ = Location.objects.get_or_create(name=event['location']) + location.save() - try: - event_obj, new = Event.objects.get_or_create(date=datetime.strptime(date['date'], "%d.%m.%Y"), - title=event['title']) - if new: - event_obj.category = event['category'] - event_obj.link = event['link'] - event_obj.time = datetime.strptime(str(event['time']).split()[1], "%H:%M") - event_obj.locations.add(Location.objects.get(name=event['location'])) - event_obj.save() - Event.objects.filter(title="").delete() - else: - print("Event %s already exists. Start Update" % str(event_obj.title)) - event_obj.category = event['category'] - event_obj.link = event['link'] - event_obj.time = datetime.strptime(str(event['time']).split()[1], "%H:%M") - event_obj.locations.add(Location.objects.get(name=event['location'])) - event_obj.save() - except IntegrityError: - # ignored - pass + event_obj, _ = Event.objects.get_or_create(date=datetime.strptime(date['date'], "%d.%m.%Y"), + title=event['title']) + event_obj.category = event['category'] + event_obj.link = event['link'] + event_obj.time = datetime.strptime(str(event['time']).split()[1], "%H:%M") + event_obj.locations.add(Location.objects.get(name=event['location'])) + event_obj.save() + logger.info('CREATED - Event: {}'.format(event_obj.title)) def deleteUnivisObjects(): @@ -62,15 +50,13 @@ def writeUnivisLocationsInDB(rooms): for room in rooms: if '@key' in room and 'short' in room: try: - Location.objects.create(key=room['@key'], name=room['short']) - except IntegrityError: - print("Possible Duplicate! Start DB refresh") - try: - Location.objects.get(name=room['short']).key = room['@key'] - except Exception as harderr: - print("Failed to refresh object" + harderr.args) + location, _ = Location.objects.get_or_create(key=room['@key'], name=room['short']) + location.key = room['@key'] + location.name = room['short'] + location.save() + logger.info('CREATE - Location: {}'.format(location.name)) except Exception as err: - print(err.args) + logger.critical(err.args) def getLocationIDs(event): @@ -110,21 +96,24 @@ def writeUnivisEventsInDB(events: list): event_obj.orgname = event['orgname'] try: event_obj.save() + logger.info(event_obj.title) except IntegrityError: # TODO: Update DB Object if duplicate detected - print("Found Duplicate!") + logger.info("Found Duplicate!") except Exception as err: - print(err.args) + logger.exception(err.args) Event.objects.filter(title="").delete() def write_out_db_objects(): - pprint("Event: " + str(Event.objects.count())) - pprint("Location: " + str(Location.objects.count())) + return "\n\tEvent: {event}\n\tLocation: {location}".format( + event=Event.objects.count(), + location=Location.objects.count(), + ) def main(): - print("Aktueller Stand:") + logger.info("Aktueller Stand:") write_out_db_objects() # deleteUnivisObjects() @@ -139,7 +128,7 @@ def main(): writeFekideDataInDB(fekide_eventpage_parser.parsePage()) - print("Neuer Stand:") + logger.info("Neuer Stand:") write_out_db_objects() diff --git a/ofu_app/apps/food/admin.py b/ofu_app/apps/food/admin.py index f36ae24..40c25e2 100644 --- a/ofu_app/apps/food/admin.py +++ b/ofu_app/apps/food/admin.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.contrib import admin -from apps.food.models import SingleFood, Menu, HappyHour, UserRating, UserFoodImage, FoodImage +from apps.food.models import SingleFood, Menu, HappyHour, UserFoodRating, UserFoodImage, FoodImage, UserFoodComment class SingleFoodInline(admin.TabularInline): @@ -22,7 +22,8 @@ class MenuAdmin(admin.ModelAdmin): # Register your models here. admin.site.register(SingleFood) admin.site.register(HappyHour) -admin.site.register(UserRating) +admin.site.register(UserFoodRating) admin.site.register(UserFoodImage) +admin.site.register(UserFoodComment) admin.site.register(Menu, MenuAdmin) admin.site.register(FoodImage) diff --git a/ofu_app/apps/food/api/v1_1/__init__.py b/ofu_app/apps/food/api/v1_1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ofu_app/apps/food/api/serializers.py b/ofu_app/apps/food/api/v1_1/serializers.py similarity index 100% rename from ofu_app/apps/food/api/serializers.py rename to ofu_app/apps/food/api/v1_1/serializers.py diff --git a/ofu_app/apps/food/api/urls.py b/ofu_app/apps/food/api/v1_1/urls.py similarity index 97% rename from ofu_app/apps/food/api/urls.py rename to ofu_app/apps/food/api/v1_1/urls.py index 66bcd5b..ae7cae2 100644 --- a/ofu_app/apps/food/api/urls.py +++ b/ofu_app/apps/food/api/v1_1/urls.py @@ -14,7 +14,7 @@ Including another URLconf 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url -from apps.food.api import views as api_views +from apps.food.api.v1_1 import views as api_views from apps.food.models import Menu urlpatterns = [ diff --git a/ofu_app/apps/food/api/views.py b/ofu_app/apps/food/api/v1_1/views.py similarity index 97% rename from ofu_app/apps/food/api/views.py rename to ofu_app/apps/food/api/v1_1/views.py index 5e73da7..25e8809 100644 --- a/ofu_app/apps/food/api/views.py +++ b/ofu_app/apps/food/api/v1_1/views.py @@ -4,11 +4,11 @@ from __future__ import unicode_literals from datetime import datetime from datetime import timedelta -from apps.food.api.serializers import MenuSerializer, HappyHourSerializer +from apps.food.api.v1_1.serializers import MenuSerializer, HappyHourSerializer from apps.food.models import Menu, HappyHour from rest_framework import viewsets -from rest_framework.decorators import api_view, permission_classes +from rest_framework.decorators import permission_classes from rest_framework.permissions import AllowAny diff --git a/ofu_app/apps/food/api/v1_2/__init__.py b/ofu_app/apps/food/api/v1_2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ofu_app/apps/food/api/v1_2/serializers/__init__.py b/ofu_app/apps/food/api/v1_2/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ofu_app/apps/food/api/v1_2/serializers/main_serializers.py b/ofu_app/apps/food/api/v1_2/serializers/main_serializers.py new file mode 100644 index 0000000..f69e118 --- /dev/null +++ b/ofu_app/apps/food/api/v1_2/serializers/main_serializers.py @@ -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') diff --git a/ofu_app/apps/food/api/v1_2/serializers/user_serializers.py b/ofu_app/apps/food/api/v1_2/serializers/user_serializers.py new file mode 100644 index 0000000..032cf7f --- /dev/null +++ b/ofu_app/apps/food/api/v1_2/serializers/user_serializers.py @@ -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 diff --git a/ofu_app/apps/food/api/v1_2/urls.py b/ofu_app/apps/food/api/v1_2/urls.py new file mode 100644 index 0000000..89dca3a --- /dev/null +++ b/ofu_app/apps/food/api/v1_2/urls.py @@ -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//', 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/', api_views.ApiMeal.as_view(), name='meal'), + path('food/meals//comments', api_views.ApiMealComments.as_view(), name='meal-comments'), + + path('food/meals//comment', user_api_views.ApiUserFoodCommentUpload.as_view(), name='meals-comment-upload'), + path('food/meals//rating', user_api_views.ApiFoodRatingUpload.as_view(), name='meals-rating-upload'), + path('food/meals//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/', api_views.ApiHappyHours.as_view(), name='happy-hours'), + path('food/happy-hours/locations', api_views.ApiHappyHoursLocations.as_view(), name='happy-hours-locations'), +] diff --git a/ofu_app/apps/food/api/v1_2/views/__init__.py b/ofu_app/apps/food/api/v1_2/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ofu_app/apps/food/api/v1_2/views/main_views.py b/ofu_app/apps/food/api/v1_2/views/main_views.py new file mode 100644 index 0000000..c5cf761 --- /dev/null +++ b/ofu_app/apps/food/api/v1_2/views/main_views.py @@ -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) diff --git a/ofu_app/apps/food/api/v1_2/views/user_views.py b/ofu_app/apps/food/api/v1_2/views/user_views.py new file mode 100644 index 0000000..51e9fb4 --- /dev/null +++ b/ofu_app/apps/food/api/v1_2/views/user_views.py @@ -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() +# +# diff --git a/ofu_app/apps/food/management/commands/delete_food.py b/ofu_app/apps/food/management/commands/delete_food.py index 491119a..da72afe 100644 --- a/ofu_app/apps/food/management/commands/delete_food.py +++ b/ofu_app/apps/food/management/commands/delete_food.py @@ -1,5 +1,5 @@ -from django.core.management.base import BaseCommand, CommandError -from apps.food.models import Menu, HappyHour, SingleFood +from django.core.management.base import BaseCommand + from apps.food.utils import migrate_data diff --git a/ofu_app/apps/food/migrations/0009_auto_20180324_0039.py b/ofu_app/apps/food/migrations/0009_auto_20180324_0039.py new file mode 100644 index 0000000..42b1ca9 --- /dev/null +++ b/ofu_app/apps/food/migrations/0009_auto_20180324_0039.py @@ -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')}, + ), + ] diff --git a/ofu_app/apps/food/migrations/0010_auto_20180325_1630.py b/ofu_app/apps/food/migrations/0010_auto_20180325_1630.py new file mode 100644 index 0000000..ac00a0c --- /dev/null +++ b/ofu_app/apps/food/migrations/0010_auto_20180325_1630.py @@ -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'), + ), + ] diff --git a/ofu_app/apps/food/migrations/0011_auto_20180326_2255.py b/ofu_app/apps/food/migrations/0011_auto_20180326_2255.py new file mode 100644 index 0000000..a298e24 --- /dev/null +++ b/ofu_app/apps/food/migrations/0011_auto_20180326_2255.py @@ -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')}, + ), + ] diff --git a/ofu_app/apps/food/migrations/0012_auto_20180326_2343.py b/ofu_app/apps/food/migrations/0012_auto_20180326_2343.py new file mode 100644 index 0000000..031dcff --- /dev/null +++ b/ofu_app/apps/food/migrations/0012_auto_20180326_2343.py @@ -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)]), + ), + ] diff --git a/ofu_app/apps/food/migrations/0013_auto_20180330_1045.py b/ofu_app/apps/food/migrations/0013_auto_20180330_1045.py new file mode 100644 index 0000000..7b40158 --- /dev/null +++ b/ofu_app/apps/food/migrations/0013_auto_20180330_1045.py @@ -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, + ), + ] diff --git a/ofu_app/apps/food/migrations/0014_singlefood_comments.py b/ofu_app/apps/food/migrations/0014_singlefood_comments.py new file mode 100644 index 0000000..b43e94e --- /dev/null +++ b/ofu_app/apps/food/migrations/0014_singlefood_comments.py @@ -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'), + ), + ] diff --git a/ofu_app/apps/food/migrations/0015_auto_20180331_1427.py b/ofu_app/apps/food/migrations/0015_auto_20180331_1427.py new file mode 100644 index 0000000..11a9c44 --- /dev/null +++ b/ofu_app/apps/food/migrations/0015_auto_20180331_1427.py @@ -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, + ), + ] diff --git a/ofu_app/apps/food/migrations/0016_auto_20180331_1446.py b/ofu_app/apps/food/migrations/0016_auto_20180331_1446.py new file mode 100644 index 0000000..baaf79c --- /dev/null +++ b/ofu_app/apps/food/migrations/0016_auto_20180331_1446.py @@ -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'), + ), + ] diff --git a/ofu_app/apps/food/migrations/0017_foodimage_thumb.py b/ofu_app/apps/food/migrations/0017_foodimage_thumb.py new file mode 100644 index 0000000..f126492 --- /dev/null +++ b/ofu_app/apps/food/migrations/0017_foodimage_thumb.py @@ -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'), + ), + ] diff --git a/ofu_app/apps/food/migrations/0018_remove_foodimage_thumb.py b/ofu_app/apps/food/migrations/0018_remove_foodimage_thumb.py new file mode 100644 index 0000000..e205cce --- /dev/null +++ b/ofu_app/apps/food/migrations/0018_remove_foodimage_thumb.py @@ -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', + ), + ] diff --git a/ofu_app/apps/food/migrations/0019_foodimage_thumb.py b/ofu_app/apps/food/migrations/0019_foodimage_thumb.py new file mode 100644 index 0000000..d4fd788 --- /dev/null +++ b/ofu_app/apps/food/migrations/0019_foodimage_thumb.py @@ -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), + ), + ] diff --git a/ofu_app/apps/food/models.py b/ofu_app/apps/food/models.py index 89e2916..6e74977 100644 --- a/ofu_app/apps/food/models.py +++ b/ofu_app/apps/food/models.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals import os from io import BytesIO +import uuid +from _datetime import datetime from PIL import Image from django.conf import settings @@ -10,6 +12,7 @@ from django.contrib.auth.models import User from django.core.files.uploadedfile import SimpleUploadedFile from django.db import models from django.utils import timezone +from django.core.validators import MaxValueValidator, MinValueValidator MAX_LENGTH = 256 MAX_FOOD_NAME = 256 @@ -18,6 +21,20 @@ MAX_FOOD_PRICE_LENGTH = 10 MAX_FOOD_ALLERGENNAME_LENGTH = 256 MAX_HAPPY_HOUR_LOCATION_LENGTH = 256 MAX_HAPPY_HOUR_DESCRIPTION_LENGTH = 1024 +MAX_FOOD_COMMENT_TITLE_LENGTH = 128 +MAX_FOOD_COMMENT_LENGTH = 2048 + + +def image_path(instance, filename): + extension = filename.split('.')[-1] + date = datetime.now().strftime('%Y/%m/%W') + return 'food/originals/{}/{}.{}'.format(date, uuid.uuid4(), extension) + + +def thumb_path(instance, filename): + extension = filename.split('.')[-1] + date = datetime.now().strftime('%Y/%m/%W') + return 'food/thumbs/{}/{}.{}'.format(date, uuid.uuid4(), 'jpg') # Create your models here. @@ -29,6 +46,13 @@ class Menu(models.Model): LOCATION_CHOICES = ( (ERBA, 'Erba'), (MARKUSPLATZ, 'Markusplatz'), (FEKI, 'Feldkirchenstrasse'), (AUSTRASSE, 'Austrasse')) + + # Api location data + API_LOCATIONS = [{'id': FEKI, 'name': 'Feldkirchenstrasse', 'short': 'Feki'}, + {'id': AUSTRASSE, 'name': 'Austrasse', 'short': 'Austrasse'}, + {'id': ERBA, 'name': 'Erba', 'short': 'Erba'}, + {'id': MARKUSPLATZ, 'name': 'Markusplatz', 'short': 'Markusplatz'}, ] + id = models.AutoField(primary_key=True) date = models.DateField(default=timezone.now) location = models.CharField(max_length=MAX_FOOD_LOCATION_LENGTH, choices=LOCATION_CHOICES) @@ -47,9 +71,10 @@ class SingleFood(models.Model): price_student = models.CharField(max_length=MAX_FOOD_PRICE_LENGTH, blank=True, null=True) price_employee = models.CharField(max_length=MAX_FOOD_PRICE_LENGTH, blank=True, null=True) price_guest = models.CharField(max_length=MAX_FOOD_PRICE_LENGTH, blank=True, null=True) - image = models.ForeignKey('FoodImage', on_delete=models.PROTECT, blank=True, null=True) + image = models.ForeignKey('FoodImage', on_delete=models.SET_NULL, blank=True, null=True) rating = models.FloatField(default=0) allergens = models.ManyToManyField("Allergene", blank=True) + comments = models.ManyToManyField('UserFoodComment', blank=True) def __str__(self): return "%s - Rating: %f - Student Price: %s" % (self.name, self.rating, self.price_student) @@ -68,22 +93,32 @@ class HappyHour(models.Model): date = models.DateField(default=timezone.now) starttime = models.TimeField(default=timezone.now) endtime = models.TimeField(default=timezone.now) - location = models.CharField(max_length=MAX_HAPPY_HOUR_LOCATION_LENGTH) + location = models.ForeignKey('HappyHourLocation', on_delete=models.PROTECT) description = models.CharField(max_length=MAX_HAPPY_HOUR_DESCRIPTION_LENGTH) class Meta: - # TODO: unique description instead of date - unique_together = ('date', 'location', 'starttime', 'endtime') + unique_together = ('location', 'starttime', 'endtime') def __str__(self): return "Date: %s, Location: %s" % (self.date.strftime("%Y.%m.%d"), self.location) -class UserRating(models.Model): +class HappyHourLocation(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(unique=True, max_length=MAX_HAPPY_HOUR_LOCATION_LENGTH) + + def __str__(self): + return "%s" % self.name + + +class UserFoodRating(models.Model): id = models.AutoField(primary_key=True) user = models.ForeignKey(User, on_delete=models.PROTECT, unique=False) food = models.ForeignKey(SingleFood, on_delete=models.PROTECT) - rating = models.FloatField(default=0) + rating = models.FloatField(default=0, validators=[MaxValueValidator(5), MinValueValidator(0)]) + + class Meta: + unique_together = ('user', 'food') def __str__(self): return "User: %s - Rating: %s" % (self.user.username, self.rating) @@ -102,16 +137,31 @@ class UserFoodImage(models.Model): return "User: %s - Image: %s" % (self.user.username, str(self.image)) +class UserFoodComment(models.Model): + id = models.AutoField(primary_key=True) + user = models.ForeignKey(User, on_delete=models.PROTECT, unique=False) + food = models.ForeignKey(SingleFood, on_delete=models.PROTECT) + title = models.CharField(max_length=MAX_FOOD_COMMENT_TITLE_LENGTH, null=False, blank=False) + description = models.CharField(max_length=MAX_FOOD_COMMENT_LENGTH, null=False, blank=False) + + class Meta: + unique_together = ('user', 'food') + + def __str__(self): + return "User: %s - Title: %s" % (self.user.username, self.title) + + class FoodImage(models.Model): id = models.AutoField(primary_key=True) - image = models.ImageField(upload_to='food/originals/%Y/%m/%W', blank=True, null=True) - thumb = models.ImageField(upload_to='food/thumbs/%Y/%m/%W', blank=True, null=True) + image = models.ImageField(upload_to=image_path, blank=False, null=False) + thumb = models.ImageField(upload_to=thumb_path, blank=True, null=True) - def save(self, force_update=False, force_insert=False, thumb_size=(640, 480)): + def save(self, *args, **kwargs): image = Image.open(self.image) if image.mode not in ('L', 'RGB'): image = image.convert('RGB') + thumb_size = (128, 128) image.thumbnail(thumb_size, Image.ANTIALIAS) # save the thumbnail to memory @@ -124,12 +174,17 @@ class FoodImage(models.Model): temp_handle.read(), content_type='image/jpg') - self.thumb.save('%s_thumbnail.%s' % (self.id, 'jpg'), suf, save=False) - # save the image object - self.image.name = "%s_original.%s" % (self.id, 'jpg') - super(FoodImage, self).save(force_update, force_insert) + # self.thumb.save('%s_thumbnail.%s' % (self.id, 'jpg'), suf, save=False) + self.thumb.save(name='', content=suf, save=False) - def delete(self, using=None, keep_parents=False): - os.remove(os.path.join(settings.MEDIA_ROOT, self.image.name)) - os.remove(os.path.join(settings.MEDIA_ROOT, self.thumb.name)) - super(FoodImage, self).delete() + # save the image object + super(FoodImage, self).save(*args, **kwargs) + + # + # def delete(self, using=None, keep_parents=False): + # os.remove(os.path.join(settings.MEDIA_ROOT, self.image.name)) + # os.remove(os.path.join(settings.MEDIA_ROOT, self.thumb.name)) + # super(FoodImage, self).delete() + + def __str__(self): + return "Image: %s" % (str(self.image)) diff --git a/ofu_app/apps/food/templates/__init__.py b/ofu_app/apps/food/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ofu_app/apps/food/templates/root_api.jinja b/ofu_app/apps/food/templates/root_api.jinja new file mode 100644 index 0000000..61d1f65 --- /dev/null +++ b/ofu_app/apps/food/templates/root_api.jinja @@ -0,0 +1,126 @@ +{% import '/macros/nav.jinja' as nav %} +{# ===== HTML ===== #} + + +{# ===== Head ===== #} + + + + + BaStA + + + + {% block js_extra %}{% endblock %} + + + + + + {% block css_extra %}{% endblock %} + + + + + +{# ===== Body ===== #} + +{% block body %} +
+
+
+ {% if request.user.is_authenticated %} + + {% else %} + + {% endif %} +
+
+ {% block headline %}{% endblock %}
+
+ +
+
+
{% block bottom_nav %}{% endblock %}
+
+ {% block content %}{% endblock %} +
+ + + +
MethodRequest URLDescription
+
+
+ + {% block test %} +
+
+ Hinweis: Diese Seite dient nur zu Testzwecken. + Wir garantieren weder die Vollständigkeit, noch + die Korrektheit der dargestellten Daten. +
+
+ {% endblock %} + {% block footer %} + + + {% endblock %} +
+{% endblock %} + + + + + +{% block js_tail %}{% endblock %} + + \ No newline at end of file diff --git a/ofu_app/apps/food/tests/tests_api.py b/ofu_app/apps/food/tests/tests_api.py index 18d16a4..10a562b 100644 --- a/ofu_app/apps/food/tests/tests_api.py +++ b/ofu_app/apps/food/tests/tests_api.py @@ -4,9 +4,8 @@ from __future__ import unicode_literals from django.test import TestCase from django.urls import reverse from apps.food.models import SingleFood, Menu -from apps.food.api.serializers import MenuSerializer +from apps.food.api.v1_1.serializers import MenuSerializer from rest_framework import status -from rest_framework.test import APIRequestFactory from datetime import datetime diff --git a/ofu_app/apps/food/urls.py b/ofu_app/apps/food/urls.py index 571dfa8..f85beb9 100644 --- a/ofu_app/apps/food/urls.py +++ b/ofu_app/apps/food/urls.py @@ -13,12 +13,11 @@ Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ -from django.conf.urls import url, include from rest_framework import routers from apps.food import views, admin_views -from django.conf.urls import url, include -from apps.food.api import views as api_views +from django.conf.urls import url +from apps.food.api.v1_1 import views as api_views # API Version 1.0 apiRouter_v1 = routers.DefaultRouter() diff --git a/ofu_app/apps/food/utils/migrate_data.py b/ofu_app/apps/food/utils/migrate_data.py index 2cc11e2..6d6c43e 100644 --- a/ofu_app/apps/food/utils/migrate_data.py +++ b/ofu_app/apps/food/utils/migrate_data.py @@ -2,8 +2,11 @@ import json from datetime import datetime from pprint import pprint from django.db.utils import IntegrityError -from apps.food.models import SingleFood, Menu, HappyHour, Allergene +from apps.food.models import SingleFood, Menu, HappyHour, Allergene, HappyHourLocation from apps.food.utils.parser import mensa_page_parser, fekide_happyhour_page_parser, cafete_page_parser +import logging + +logger = logging.getLogger(__name__) # CONFIG SERVICE LINKS LINK_FEKI_MENSA = "https://www.studentenwerk-wuerzburg.de/bamberg/essen-trinken/speiseplaene.html?tx_thmensamenu_pi2%5Bmensen%5D=3&tx_thmensamenu_pi2%5Baction%5D=show&tx_thmensamenu_pi2%5Bcontroller%5D=Speiseplan&cHash=c3fe5ebb35e5fba3794f01878e798b7c" @@ -15,128 +18,112 @@ LINK_FEKIDE_GUIDE = "https://www.feki.de/happyhour" LOCATION_NAMES = ('erba', 'markusplatz', 'feldkirchenstraße', 'austraße') -def getJsonFromFile(path): - with open(path, "r") as file: - return json.load(file) - - def getLocation(raw_loc): for choice, name in zip(Menu.LOCATION_CHOICES, LOCATION_NAMES): - print(name.upper() in str(raw_loc).upper()) - if (name.upper() in str(raw_loc).upper()): + if name.upper() in str(raw_loc).upper(): return choice - - print("LOCATION NOT FOUND") + logger.warning("{loc} unknown location".format(loc=raw_loc)) + return None def writeStudentenwerkDataInDB(data): - data = json.loads(data) - pprint(data) + if not data: + logger.warning('no data') + return + logger.info("{location}".format(location=data['name'])) for menu in data['weekmenu']: - pprint(menu) + logger.info("{date}".format(date=menu['date'])) foodlist = [] for single_food in menu['menu']: - pprint(single_food) + logger.info("{}".format(single_food['title'])) + allergens = [] if 'allergens' in single_food: - allergens = [] for allergen in single_food['allergens']: - try: - allergens.append(Allergene.objects.create(name=allergen)) - except IntegrityError: - allergens.append(Allergene.objects.get(name=allergen)) + allergens.append(Allergene.objects.get_or_create(name=allergen)[0]) + # TODO: Consider keyword arg for price try: - if 'prices' in single_food: - if 'price_student' in single_food['prices']: - price_student = single_food['prices']['price_student'] - else: - price_student = "None" - if 'price_employee' in single_food['prices']: - price_employee = single_food['prices']['price_employee'] - else: - price_employee = "None" - if 'price_guest' in single_food['prices']: - price_guest = single_food['prices']['price_guest'] - else: - price_guest = "None" - db_single_food = SingleFood.objects.create(name=single_food['title'], - price_student=price_student, - price_employee=price_employee, - price_guest=price_guest) - else: - db_single_food = SingleFood.objects.create(name=single_food['title']) - if 'allergens' in locals(): - db_single_food.allergens.set(allergens) - foodlist.append(db_single_food) - except IntegrityError: - db_single_food = SingleFood.objects.get(name=single_food['title']) + db_single_food, created = SingleFood.objects.get_or_create(name=single_food['title']) if 'prices' in single_food: if 'price_student' in single_food['prices']: db_single_food.price_student = single_food['prices']['price_student'] + else: + db_single_food.price_student = "None" if 'price_employee' in single_food['prices']: db_single_food.price_employee = single_food['prices']['price_employee'] + else: + db_single_food.price_employee = "None" if 'price_guest' in single_food['prices']: db_single_food.price_guest = single_food['prices']['price_guest'] - if 'allergens' in locals(): + else: + db_single_food.price_guest = "None" + if allergens: db_single_food.allergens.set(allergens) foodlist.append(db_single_food) - try: + db_single_food.save() + except IntegrityError as e: + logger.exception(e) + try: date = datetime.strptime(str(menu['date']), "%d.%m.").replace(year=datetime.today().year) - menu = Menu.objects.create(location=getLocation(data['name']), date=date) + menu, _ = Menu.objects.get_or_create(location=getLocation(data['name']), date=date) menu.menu.set(foodlist) menu.save() except IntegrityError as error: - # ignored - pass + logger.exception(error) def writeFekideDataInDB(data): for happyhour_data in data['happyhours']: time = str(happyhour_data['time']).replace(" ", "").split("-") - happyhour, new = HappyHour.objects.get_or_create(date=datetime.strptime(data['day'], "%A, %d.%m.%Y"), - location=happyhour_data['location'], - description=happyhour_data['description'], - starttime=datetime.strptime(time[0], "%H:%M").time(), - endtime=datetime.strptime(time[1], "%H:%M").time()) - if not new: + try: + location, _ = HappyHourLocation.objects.get_or_create(name=happyhour_data['location']) + happyhour, _ = HappyHour.objects.get_or_create(location=location, + starttime=datetime.strptime(time[0], "%H:%M").time(), + endtime=datetime.strptime(time[1], "%H:%M").time()) happyhour.date = datetime.strptime(data['day'], "%A, %d.%m.%Y") - happyhour.location = happyhour_data['location'] happyhour.description = happyhour_data['description'] - happyhour.starttime = datetime.strptime(time[0], "%H:%M").time() - happyhour.endtime = datetime.strptime(time[1], "%H:%M").time() happyhour.save() - print("%s: Happy Hour: Location: %s, Description: %s" % ( - str(happyhour.date.date()), str(happyhour.location), str(happyhour.description))) + logger.info("{date}: Happy Hour: Location: {location}, Description: {description}".format( + date=happyhour.date, + location=happyhour.location, + description=happyhour.description) + ) + except Exception as e: + logger.exception(e) def writeoutDBObjects(): - pprint("SingleFood: " + str(SingleFood.objects.count())) - pprint("Menu: " + str(Menu.objects.count())) - pprint("HappyHour: " + str(HappyHour.objects.count())) + return "\n\tSingleFood: {single_food}\n\tMenu: {menu}\n\tHappyHour: {happy_hour}".format( + single_food=SingleFood.objects.count(), + menu=Menu.objects.count(), + happy_hour=HappyHour.objects.count() + ) def delete(): happy_hours = HappyHour.objects.all() - print("Deleted following Happy Hours:") + logger.info("Deleted following Happy Hours:") for happy_hour in happy_hours: - print("%s: Happy Hour: Location: %s, Description: %s" % ( - str(happy_hour.date), str(happy_hour.location), str(happy_hour.description))) + logger.info("{date}: Happy Hour: Location: {location}, Description: {description}".format( + date=happy_hour.date, + location=happy_hour.location, + description=happy_hour.description) + ) happy_hour.delete() def main(): - print("Aktueller Stand:") - writeoutDBObjects() + logger.info("Aktueller Stand:" + writeoutDBObjects()) + # get food jsons writeStudentenwerkDataInDB(mensa_page_parser.parsePage(LINK_AUSTR_MENSA)) writeStudentenwerkDataInDB(mensa_page_parser.parsePage(LINK_FEKI_MENSA)) - writeStudentenwerkDataInDB(cafete_page_parser.parsePage(LINK_ERBA_CAFETE)) - writeStudentenwerkDataInDB(cafete_page_parser.parsePage(LINK_MARKUS_CAFETE)) - writeFekideDataInDB(fekide_happyhour_page_parser.parsePage(LINK_FEKIDE_GUIDE)) + writeStudentenwerkDataInDB(cafete_page_parser.parse_page(LINK_ERBA_CAFETE)) + writeStudentenwerkDataInDB(cafete_page_parser.parse_page(LINK_MARKUS_CAFETE)) + writeFekideDataInDB(fekide_happyhour_page_parser.parse_page(LINK_FEKIDE_GUIDE)) - print("Neuer Stand:") - writeoutDBObjects() + logger.info("Neuer Stand:" + writeoutDBObjects()) if __name__ == '__main__': diff --git a/ofu_app/apps/food/utils/parser/__init__.py b/ofu_app/apps/food/utils/parser/__init__.py index e69de29..4b1276c 100644 --- a/ofu_app/apps/food/utils/parser/__init__.py +++ b/ofu_app/apps/food/utils/parser/__init__.py @@ -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 diff --git a/ofu_app/apps/food/utils/parser/cafete_page_parser.py b/ofu_app/apps/food/utils/parser/cafete_page_parser.py index a11fe37..5c1ef6d 100644 --- a/ofu_app/apps/food/utils/parser/cafete_page_parser.py +++ b/ofu_app/apps/food/utils/parser/cafete_page_parser.py @@ -1,23 +1,22 @@ -import requests -from bs4 import BeautifulSoup -import json import datetime +import logging import re -from pprint import pprint + +from bs4 import BeautifulSoup + +from . import load_page + +logger = logging.getLogger(__name__) SPEISEPLAN_NAME_SELECTOR = '.csc-default .csc-header .csc-firstHeader' -def loadPage(url: str): - return requests.get(url).content - - -def getFoodplanName(soup): +def get_foodplan_name(soup): foodplan_name = soup.select(SPEISEPLAN_NAME_SELECTOR)[0].getText() return foodplan_name -def getRightLine(lines): +def get_right_line(lines): foodlines = [] pattern = re.compile("[0-9]+.+[A-Z]+") for line in list(lines): @@ -27,42 +26,42 @@ def getRightLine(lines): return foodlines -def getFoodPerDay(soup): +def get_food_per_day(soup): days = [] lines = soup.select('.csc-default .bodytext') - foodlines = getRightLine(lines) + foodlines = get_right_line(lines) for food in foodlines: - dayObj = {} day = str(food).split()[0] - foodName = str(food).replace(day, "").strip() - singleFoodObj = {} - singleFoodObj['title'] = foodName - dayObj['date'] = day - dayObj['menu'] = [singleFoodObj] - days.append(dayObj) + food_name = str(food).replace(day, "").strip() + single_food_obj = {'title': food_name} + day_obj = { + 'date': day, + 'menu': [single_food_obj] + } + days.append(day_obj) return days -def parsePage(url: str): +def parse_page(url: str): pagecontent = {} # {mensaspeiseplan: # {name:"", # weekmenu: [day:{date:, menu:[,,,]}] # } # } + try: + page = load_page(url) + soup = BeautifulSoup(page, "lxml") + foodplan_name = get_foodplan_name(soup) - page = loadPage(url) - mensaSpeiseplan = {} - soup = BeautifulSoup(page, "lxml") - foodplan_name = getFoodplanName(soup) - - days = getFoodPerDay(soup) - mensaSpeiseplan['weekmenu'] = days - mensaSpeiseplan['name'] = foodplan_name - mensaSpeiseplan['execution_time'] = datetime.datetime.today().strftime("%A, %d.%m.%Y") - mensaSpeiseplanJson = json.dumps(mensaSpeiseplan) - return mensaSpeiseplanJson - + days = get_food_per_day(soup) + return { + 'weekmenu': days, + 'name': foodplan_name, + 'execution_time': datetime.datetime.today().strftime("%A, %d.%m.%Y") + } + except Exception as e: + logger.exception(e) + return None # LINK_ERBA_CAFETE = "https://www.studentenwerk-wuerzburg.de/bamberg/essen-trinken/sonderspeiseplaene/cafeteria-erba-insel.html" -# pprint(parsePage(LINK_ERBA_CAFETE)) diff --git a/ofu_app/apps/food/utils/parser/fekide_happyhour_page_parser.py b/ofu_app/apps/food/utils/parser/fekide_happyhour_page_parser.py index 3af47a2..50d6049 100644 --- a/ofu_app/apps/food/utils/parser/fekide_happyhour_page_parser.py +++ b/ofu_app/apps/food/utils/parser/fekide_happyhour_page_parser.py @@ -1,51 +1,53 @@ -import requests -from bs4 import BeautifulSoup import datetime -import json +import logging + +from bs4 import BeautifulSoup + +from . import load_page + +logger = logging.getLogger(__name__) SPEISEPLAN_NAME_SELECTOR = '.csc-default .csc-header .csc-firstHeader' -def loadPage(url: str): - return requests.get(url).content - - -def getDay(): +def get_day(): return datetime.datetime.today().strftime("%A, %d.%m.%Y") -def getHappyHours(soup): +def get_happy_hours(soup): happyhours = [] happyhourstable = soup.select('#food .table tr') for tableline in happyhourstable: - happyhour = {} linesoup = BeautifulSoup(str(tableline), "lxml") location = linesoup.find("td", {"class": "location"}).getText() time = linesoup.find("td", {"class": "time"}).getText() description = linesoup.find("td", {"class": "description"}).getText() description = str(description).strip() - - happyhour['location'] = location - happyhour['time'] = time - happyhour['description'] = description + happyhour = { + 'location': location, + 'time': time, + 'description': description + } happyhours.append(happyhour) return happyhours -def parsePage(url: str): - pagecontent = {} +def parse_page(url: str): # { # happyhours:[{happyhour:{location: "",time: "",description: ""},,,,] # } happyhours = [] - - page = loadPage(url) - soup = BeautifulSoup(page, "lxml") - happyhours = getHappyHours(soup) - pagecontent['happyhours'] = happyhours - pagecontent['day'] = getDay() - pagecontent['execution_time'] = datetime.datetime.today().strftime("%A, %d.%m.%Y") - return pagecontent - + try: + page = load_page(url) + soup = BeautifulSoup(page, "lxml") + happyhours = get_happy_hours(soup) + return { + 'happyhours': happyhours, + 'day': get_day(), + 'execution_time': datetime.datetime.today().strftime("%A, %d.%m.%Y") + } + except Exception as e: + logger.exception(e) + return None # LINK_FEKIDE_GUIDE = "https://www.feki.de/happyhour/wochenuebersicht" # parsePage(LINK_FEKIDE_GUIDE) diff --git a/ofu_app/apps/food/utils/parser/mensa_page_parser.py b/ofu_app/apps/food/utils/parser/mensa_page_parser.py index dad1349..74620c6 100644 --- a/ofu_app/apps/food/utils/parser/mensa_page_parser.py +++ b/ofu_app/apps/food/utils/parser/mensa_page_parser.py @@ -1,14 +1,11 @@ -import requests -from bs4 import BeautifulSoup -import json import datetime +import logging +from bs4 import BeautifulSoup -# FEKI_URL = "https://www.studentenwerk-wuerzburg.de/bamberg/essen-trinken/speiseplaene.html?tx_thmensamenu_pi2%5Bmensen%5D=3&tx_thmensamenu_pi2%5Baction%5D=show&tx_thmensamenu_pi2%5Bcontroller%5D=Speiseplan&cHash=c3fe5ebb35e5fba3794f01878e798b7c" +from . import load_page - -def loadPage(url: str): - return requests.get(url).content +logger = logging.getLogger(__name__) def getMenuDay(soup): @@ -18,12 +15,10 @@ def getMenuDay(soup): def getFoodPerDay(soup): week_menus = [] for day in soup.select('.currentweek .day'): - menu = {} daysoup = BeautifulSoup(str(day), "lxml") day = getMenuDay(daysoup) day_menu = [] for singleFood in daysoup.select('.menuwrap .menu'): - singleFoodObj = {} singleFoodSoup = BeautifulSoup(str(singleFood), "lxml") title = singleFoodSoup.find('div', {'class': 'title'}).getText() allergens = [e.getText() for e in singleFoodSoup.select('.left .additnr .toggler ul li')] @@ -34,13 +29,16 @@ def getFoodPerDay(soup): prices['price_employee'] = singleFoodSoup.select('.price')[0]['data-bed'] if singleFoodSoup.select('.price'): prices['price_guest'] = singleFoodSoup.select('.price')[0]['data-guest'] - singleFoodObj['title'] = title - singleFoodObj['allergens'] = allergens - singleFoodObj['prices'] = prices - day_menu.append(singleFoodObj) - - menu['date'] = str(day).split(" ")[1] - menu['menu'] = day_menu + single_food_obj = { + 'title': title, + 'allergens': allergens, + 'prices': prices + } + day_menu.append(single_food_obj) + menu = { + 'date': str(day).split(" ")[1], + 'menu': day_menu + } week_menus.append(menu) return week_menus @@ -52,16 +50,19 @@ def parsePage(url: str): # weekmenu: [day:{date:, menu:[,,,]}] # } # } - mensaSpeiseplan = {} - page = loadPage(url) - soup = BeautifulSoup(page, "lxml") - foodplan_name = getFoodplanName(soup) - days = getFoodPerDay(soup) - mensaSpeiseplan['weekmenu'] = days - mensaSpeiseplan['name'] = foodplan_name - mensaSpeiseplan['execution_time'] = datetime.datetime.today().strftime("%A, %d.%m.%Y") - mensaSpeiseplanJson = json.dumps(mensaSpeiseplan) - return mensaSpeiseplanJson + try: + page = load_page(url) + soup = BeautifulSoup(page, "lxml") + foodplan_name = getFoodplanName(soup) + days = getFoodPerDay(soup) + return { + 'weekmenu': days, + 'name': foodplan_name, + 'execution_time': datetime.datetime.today().strftime("%A, %d.%m.%Y") + } + except Exception as e: + logger.exception(e) + return None def getFoodplanName(soup): diff --git a/ofu_app/apps/food/views.py b/ofu_app/apps/food/views.py index 399461e..b209ecd 100644 --- a/ofu_app/apps/food/views.py +++ b/ofu_app/apps/food/views.py @@ -7,7 +7,7 @@ from django.http import HttpResponse from django.shortcuts import render from apps.food.forms import UploadImageForm -from apps.food.models import Menu, HappyHour, SingleFood, UserRating, UserFoodImage, FoodImage +from apps.food.models import Menu, HappyHour, SingleFood, UserFoodRating, UserFoodImage, FoodImage # Create your views here. @@ -100,12 +100,12 @@ def food_rating(request): rating = request.GET.get('rating', None) if food_id and rating: food = SingleFood.objects.get(id=food_id) - user_rating, created = UserRating.objects.get_or_create(user=request.user, - food=food) + user_rating, created = UserFoodRating.objects.get_or_create(user=request.user, + food=food) user_rating.rating = rating user_rating.save() - food_user_ratings = UserRating.objects.all().filter(food=food) + food_user_ratings = UserFoodRating.objects.all().filter(food=food) sum = 0 for food_user_rating in food_user_ratings: sum += food_user_rating.rating diff --git a/ofu_app/apps/registration/api/__init__.py b/ofu_app/apps/registration/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ofu_app/apps/registration/api/serializers.py b/ofu_app/apps/registration/api/serializers.py new file mode 100644 index 0000000..c901e9b --- /dev/null +++ b/ofu_app/apps/registration/api/serializers.py @@ -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') diff --git a/ofu_app/apps/registration/api/urls.py b/ofu_app/apps/registration/api/urls.py new file mode 100644 index 0000000..22f901c --- /dev/null +++ b/ofu_app/apps/registration/api/urls.py @@ -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'), +] diff --git a/ofu_app/apps/registration/api/views.py b/ofu_app/apps/registration/api/views.py new file mode 100644 index 0000000..322c2ca --- /dev/null +++ b/ofu_app/apps/registration/api/views.py @@ -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 diff --git a/ofu_app/apps/registration/urls.py b/ofu_app/apps/registration/urls.py index 1b19141..476c9e6 100644 --- a/ofu_app/apps/registration/urls.py +++ b/ofu_app/apps/registration/urls.py @@ -8,4 +8,5 @@ urlpatterns = [ url(r'^account_activation_sent/$', core_views.account_activation_sent, name='account_activation_sent'), url(r'^activate/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', core_views.activate, name='activate'), + url(r'^signup/$', core_views.signup, name='signup'), ] diff --git a/ofu_app/apps/registration/views.py b/ofu_app/apps/registration/views.py index 858803f..0dfa1d2 100644 --- a/ofu_app/apps/registration/views.py +++ b/ofu_app/apps/registration/views.py @@ -11,7 +11,7 @@ from django.utils.encoding import force_text from django.utils.http import urlsafe_base64_decode from django.core.mail import send_mail from django.shortcuts import HttpResponse, redirect -from apps.food.models import UserRating, UserFoodImage +from apps.food.models import UserFoodRating, UserFoodImage def signup(request): @@ -22,7 +22,7 @@ def signup(request): user.is_active = False user.save() current_site = request.META['HTTP_HOST'] - subject = 'Activate Your MySite Account' + subject = 'Activate Your BaStA Account' message = render_to_string('registration/account_activation_email.jinja', { 'user': user, 'domain': current_site, @@ -64,7 +64,7 @@ def account_view(request): if request.user.is_authenticated: user = request.user - food_ratings = UserRating.objects.filter(user=user).order_by('food__name') + food_ratings = UserFoodRating.objects.filter(user=user).order_by('food__name') food_images = UserFoodImage.objects.filter(user=user.id) print(food_images) diff --git a/ofu_app/core/settings.py b/ofu_app/core/settings.py index 73d2052..c05af6c 100755 --- a/ofu_app/core/settings.py +++ b/ofu_app/core/settings.py @@ -11,132 +11,48 @@ https://docs.djangoproject.com/en/1.11/ref/settings/ """ import os +import datetime -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ['SECRET_KEY'] - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = bool(os.environ.get('DEBUG', False)) -ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split() - -# Application definition - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.sites', - 'django_jinja', - 'apps.food', - 'apps.events', - 'apps.donar', - 'apps.registration', - 'rest_framework', - 'analytical', -] +DOMAIN = os.environ['DOMAIN'] +SITE_NAME = os.environ['SITE_NAME'] SITE_ID = 1 -REST_FRAMEWORK = { - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticated', - ], - 'PAGE_SIZE': 10 -} -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] +ADMINS = os.environ['ADMINS'].split() -ROOT_URLCONF = 'core.urls' -TEMPLATES = [ - { - 'BACKEND': 'django_jinja.backend.Jinja2', - 'DIRS': [ - os.path.join(BASE_DIR, 'templates'), - ], - 'APP_DIRS': True, - 'OPTIONS': { - 'environment': 'core.jinja2.environment' - }, - }, - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR,'templates')], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -WSGI_APPLICATION = 'core.wsgi.application' +SECRET_KEY = os.environ['SECRET_KEY'] -# Database -# https://docs.djangoproject.com/en/1.11/ref/settings/#databases +DEBUG = bool(os.environ.get('DEBUG', False)) -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': os.environ.get('POSTGRES_USER', ''), - 'USER': os.environ.get('POSTGRES_USER', ''), - 'PASSWORD': os.environ.get('POSTGRES_PASSWORD', ''), - 'HOST': os.environ.get('DATABASE_HOST', ''), - 'PORT': os.environ.get('DATABASE_PORT', ''), - }, - 'dev': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } -} +ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split() -# Password validation -# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators +# Sign Up E-Mail authentication +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = os.environ['EMAIL_HOST'] +EMAIL_HOST_USER = os.environ['EMAIL_HOST_USER'] +EMAIL_HOST_PASSWORD = os.environ['EMAIL_HOST_PASSWORD'] +EMAIL_PORT = os.environ['EMAIL_PORT'] +EMAIL_USE_TLS = True -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] +# TODO: more account with same email are possible? +ACCOUNT_EMAIL_UNIQUE = True +ACCOUNT_EMAIL_CONFIRMATION_REQUIRED = True -MIDDLEWARE_CLASSES = ( - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) +# Setup support for proxy headers +USE_X_FORWARDED_HOST = True +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + +# Media files should be stored here +MEDIA_ROOT = os.path.join(BASE_DIR, "media") +MEDIA_URL = '/media/' + +# monitoring +PIWIK_DOMAIN_PATH = os.environ['PIWIK_DOMAIN_PATH'] +PIWIK_SITE_ID = os.environ['PIWIK_SITE_ID'] + +LOGIN_REDIRECT_URL = 'home' # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ @@ -155,38 +71,179 @@ DATE_FORMAT = "l, d. F Y" DATETIME_FORMAT = "l, d. F Y" TIME_FORMAT = "H:i" -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.11/howto/static-files/ +ROOT_URLCONF = 'core.urls' +WSGI_APPLICATION = 'core.wsgi.application' STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, "static_files") -print(STATIC_ROOT) STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ] -# Setup support for proxy headers -USE_X_FORWARDED_HOST = True -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +# CORS +CORS_ORIGIN_ALLOW_ALL = False -# FORCE_SCRIPT_NAME = "app" -# Media files should be stored here -MEDIA_ROOT = os.path.join(BASE_DIR, "media") -MEDIA_URL = '/media/' +CORS_ORIGIN_WHITELIST = ( + 'localhost:3000', +) -# monitoring -PIWIK_DOMAIN_PATH = 'mg-server.ddns.net/piwik' -PIWIK_SITE_ID = '1' +# Application definition +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django.contrib.sites', + 'django_jinja', + 'apps.food', + 'apps.events', + 'apps.donar', + 'apps.registration', + 'rest_framework', + 'rest_framework.authtoken', + 'djoser', + 'analytical', + 'corsheaders', +] -LOGIN_REDIRECT_URL = 'home' +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.TokenAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + 'rest_framework.authentication.TokenAuthentication', + ], +} +DJOSER = { + 'SEND_ACTIVATION_EMAIL': True, + 'ACTIVATION_URL': os.environ['ACTIVATION_URL'], + 'SET_PASSWORD_RETYPE': True, + 'PASSWORD_RESET_CONFIRM_RETYPE': True, + 'PASSWORD_RESET_CONFIRM_URL': os.environ['PASSWORD_RESET_CONFIRM_URL'], + 'PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND': True, +} +MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] -# Sign Up E-Mail authentication -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -EMAIL_HOST = 'smtp.gmail.com' -EMAIL_HOST_USER = 'signup.basta@gmail.com' -EMAIL_HOST_PASSWORD = '1\SL^QzlSuP<`8gkP4Fd' -EMAIL_PORT = '587' -EMAIL_USE_TLS = True +MIDDLEWARE_CLASSES = ( + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) -ACCOUNT_EMAIL_UNIQUE = True -ACCOUNT_EMAIL_CONFIRMATION_REQUIRED = True +TEMPLATES = [ + { + 'BACKEND': 'django_jinja.backend.Jinja2', + 'DIRS': [ + os.path.join(BASE_DIR, 'templates'), + ], + 'APP_DIRS': True, + 'OPTIONS': { + 'environment': 'core.jinja2.environment' + }, + }, + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ.get('POSTGRES_USER', ''), + 'USER': os.environ.get('POSTGRES_USER', ''), + 'PASSWORD': os.environ.get('POSTGRES_PASSWORD', ''), + 'HOST': os.environ.get('DATABASE_HOST', ''), + 'PORT': os.environ.get('DATABASE_PORT', ''), + }, + 'dev': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'default': { + 'format': '%(asctime)s %(module)s [%(levelname)s]: %(message)s', + 'datefmt': '%Y-%m-%d %H:%M:%S', + } + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'default', + }, + 'file': { + 'class': 'logging.FileHandler', + 'filename': '/log/import_food.log', + 'formatter': 'default', + }, + 'mail_admins': { + 'level': 'ERROR', + 'class': 'django.utils.log.AdminEmailHandler', + }, + }, + 'loggers': { + 'apps.food.utils': { + 'handlers': ['console', 'file', 'mail_admins'], + 'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'), + }, + 'apps.donar.utils': { + 'handlers': ['console', 'file', 'mail_admins'], + 'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'), + }, + }, +} diff --git a/ofu_app/core/urls.py b/ofu_app/core/urls.py index e7a3e43..c48cc98 100755 --- a/ofu_app/core/urls.py +++ b/ofu_app/core/urls.py @@ -13,11 +13,13 @@ Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ + from core import views from django.conf.urls import url, include from django.contrib import admin from django.contrib.auth import views as auth_views from rest_framework import routers +from rest_framework.authtoken import views as token_auth_views from apps.food import urls as food_urls from django.conf import settings from django.conf.urls.static import static @@ -46,7 +48,18 @@ urlpatterns = [ url(r'^impressum/$', views.impressum, name='impressum'), # -- API -- + # -- Version 1.0 url(r'^api/v1/', include(api_router_v1.urls)), - url(r'^api/v1.1/', include('apps.food.api.urls')), - url(r'^api/auth/', include('rest_framework.urls', namespace='rest_framework')) + # -- Version 1.1 + url(r'^api/v1.1/', include('apps.food.api.v1_1.urls')), + url(r'^api/v1.1/', include('apps.registration.api.urls')), + + # -- Version 1.2 + url(r'^api/v1.2/', include('apps.food.api.v1_2.urls')), + url(r'^api/v1.2/', include('apps.registration.api.urls')), + + # -- Third Party APIs + url(r'^api/auth/', include('rest_framework.urls', namespace='rest_framework')), + url(r'^api/token-auth/', include('djoser.urls')), + url(r'^api/token-auth/', include('djoser.urls.authtoken')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/ofu_app/requirements.txt b/ofu_app/requirements.txt index 3953827..5f14e65 100644 --- a/ofu_app/requirements.txt +++ b/ofu_app/requirements.txt @@ -1,10 +1,14 @@ django==2.0.1 django-jinja==2.4.1 django-rest-framework==0.1.0 +#django-rest-auth==0.9.3 +#djangorestframework-jwt==1.11.0 +#django-allauth==0.35.0 django-analytical==2.4.0 requests==2.18.4 beautifulsoup4==4.6.0 #psycopg2==2.7.3.2 xmltodict==0.11.0 coverage==3.6 - +django-cors-headers +djoser==1.1.5 \ No newline at end of file diff --git a/ofu_app/static/css/main.css b/ofu_app/static/css/main.css new file mode 100644 index 0000000..3204286 --- /dev/null +++ b/ofu_app/static/css/main.css @@ -0,0 +1,6 @@ +.disabled, .disabled:hover { + color: #999999; + cursor: not-allowed; + opacity: 0.5; + text-decoration: none; +} \ No newline at end of file diff --git a/ofu_app/static/js/food/rating.js b/ofu_app/static/js/food/rating.js index 1a1f98e..e26da99 100644 --- a/ofu_app/static/js/food/rating.js +++ b/ofu_app/static/js/food/rating.js @@ -8,11 +8,18 @@ document.addEventListener('DOMContentLoaded', rate_init); */ function rate_init() { add_Stars(); - $('.star').on("mouseenter mouseleave", function () { + $('.star').on("mouseenter", function () { showRating(this); }).on("click", function () { console.log('Click'); sendRating(this); + }).on("mouseleave", function () { + var rating = $(this).parent().parent().parent().parent().parent().data('rating'); + var food_id = $(this).attr('class').split(' ')[0].split('-')[2]; + buildRating(food_id, rating); + console.log("leaved"); + console.log("rating" + $(this).parent().parent().parent().parent().parent().data('rating')); + console.log("food_id" + food_id); }) } diff --git a/ofu_app/templates/base.jinja b/ofu_app/templates/base.jinja index bac2e6b..42fde9c 100644 --- a/ofu_app/templates/base.jinja +++ b/ofu_app/templates/base.jinja @@ -16,6 +16,7 @@ + {% block css_extra %}{% endblock %}