diff --git a/README.md b/README.md index 231c37a..baf96a4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ This is a toy web applications for me to learn some HTML, CSS and Django. It is intended to be hosted in a private network to store and manage your personal cooking recipes. The website is in German and designed very minimalistic, i.e. no theme etc. -Additional features that could/should be added are proper security and deployment (if publicly hosted), user authentication, comments to and rating of recipes. +Version [1.x.x](https://github.com/PatReis/fav_reps/releases/tag/v1.0.0) only features the basic recipe forms. With version 2.x.x there is also a simple user and rating system. + +Additional features that could/should be added are proper security and deployment (if publicly hosted). ## Installation @@ -27,14 +29,18 @@ You can upload your recipes in a form to separate entries like ingredients, cook The recipes can be searched, sorted and also filtered by their assigned topics. They are rendered in an HTML template from the database with ingredients calculated based on the number of persons. Topics are to be created by the admin ``python manage.py createsuperuser`` . And by visiting the `/admin` url. You can also access/export the database via REST API at url `/api` and `/api/recipes` in json format. +In the latest version you have to create a user account to post your recipes and rate other users recipes. Example of the website.

- + +

+

+

- +

# Serving on Raspberry Pi diff --git a/db.sqlite3 b/db.sqlite3 index 0538824..9de3499 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/recipes_app/admin.py b/recipes_app/admin.py index b00d7b9..c480009 100644 --- a/recipes_app/admin.py +++ b/recipes_app/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin -from .models import Topic, Recipe +from .models import Topic, Recipe, Rating admin.site.register(Topic) admin.site.register(Recipe) +admin.site.register(Rating) diff --git a/recipes_app/forms.py b/recipes_app/forms.py index 525b718..1d92b5b 100644 --- a/recipes_app/forms.py +++ b/recipes_app/forms.py @@ -6,7 +6,7 @@ class RecipeForm(ModelForm): class Meta: model = Recipe fields = '__all__' - exclude = ["owner"] + exclude = ["owner", "rating_mean", "rating_count"] # widgets = { # 'name': TextInput(attrs={ # 'class': "form-control", diff --git a/recipes_app/migrations/0001_initial.py b/recipes_app/migrations/0001_initial.py index 6ee1e07..bc4793d 100644 --- a/recipes_app/migrations/0001_initial.py +++ b/recipes_app/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.7 on 2024-08-24 14:16 +# Generated by Django 5.0.7 on 2024-08-27 14:03 from django.db import migrations, models @@ -11,6 +11,19 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='Rating', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('stars', models.IntegerField(default=0)), + ('body', models.CharField(blank=True, max_length=500, null=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'ordering': ['-updated', '-created'], + }, + ), migrations.CreateModel( name='Recipe', fields=[ @@ -31,7 +44,8 @@ class Migration(migrations.Migration): ('expected_time_rest', models.FloatField(blank=True, default=0.0, null=True)), ('difficulty', models.IntegerField(blank=True, default=0, null=True)), ('image_meal', models.ImageField(blank=True, upload_to='recipes')), - ('rating', models.FloatField(blank=True, default=0.0)), + ('rating_mean', models.FloatField(blank=True, default=0.0, null=True)), + ('rating_count', models.IntegerField(blank=True, default=0, null=True)), ], options={ 'ordering': ['-updated', '-created'], diff --git a/recipes_app/migrations/0002_initial.py b/recipes_app/migrations/0002_initial.py index d731490..e6019d7 100644 --- a/recipes_app/migrations/0002_initial.py +++ b/recipes_app/migrations/0002_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.7 on 2024-08-24 14:16 +# Generated by Django 5.0.7 on 2024-08-27 14:03 import django.db.models.deletion from django.conf import settings @@ -15,11 +15,21 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AddField( + model_name='rating', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), migrations.AddField( model_name='recipe', name='owner', field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), ), + migrations.AddField( + model_name='rating', + name='recipe', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='recipes_app.recipe'), + ), migrations.AddField( model_name='recipe', name='topic', diff --git a/recipes_app/models.py b/recipes_app/models.py index 887b817..886380d 100644 --- a/recipes_app/models.py +++ b/recipes_app/models.py @@ -36,10 +36,29 @@ class Recipe(models.Model): expected_time_rest = models.FloatField(null=True, blank=True, default=0.0) difficulty = models.IntegerField(null=True, blank=True, default=0) image_meal = models.ImageField(blank=True, upload_to="recipes") - rating = models.FloatField(blank=True, default=0.0) + + rating_mean = models.FloatField(null=True, blank=True, default=0.0) + rating_count = models.IntegerField(null=True, blank=True, default=0) class Meta: ordering = ['-updated', '-created'] def __str__(self): return self.name + + +class Rating(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE) + + stars = models.IntegerField(default=0) + body = models.CharField(null=True, blank=True, max_length=500) + + updated = models.DateTimeField(auto_now=True) + created = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ['-updated', '-created'] + + def __str__(self): + return self.body \ No newline at end of file diff --git a/recipes_app/templates/recipes_app/home.html b/recipes_app/templates/recipes_app/home.html index 18d9a9d..4f30bc6 100644 --- a/recipes_app/templates/recipes_app/home.html +++ b/recipes_app/templates/recipes_app/home.html @@ -48,7 +48,7 @@

Kategorien

Sortieren

- +

@@ -79,14 +78,14 @@

Zutaten:


Zubereitung:

-
+

{{ recipe.steps|linebreaks }}


Tipps:

-
+

{{ recipe.tips|linebreaks }}

@@ -109,10 +108,36 @@

Nährwerte pro Person:

erstellt vor {{ recipe.created|timesince }}, geändert vor {{ recipe.updated|timesince }}

+
+ + + {% if request.user.is_authenticated %} +

Meine Bewertung

+
+ {% csrf_token %} + + + + + +
+
+ {% endif %} + + + +

User Bewertungen

+ {% for rate in ratings %} +

+ {{rate.stars}} Sterne von {{rate.user.username|default_if_none:"Unkown"}} : {{rate.body}} +

+ {% endfor %}
+ +
{% endblock %} \ No newline at end of file diff --git a/recipes_app/templates/recipes_app/recipe_form.html b/recipes_app/templates/recipes_app/recipe_form.html index 318439f..a18bf6f 100644 --- a/recipes_app/templates/recipes_app/recipe_form.html +++ b/recipes_app/templates/recipes_app/recipe_form.html @@ -36,10 +36,6 @@

{{title_of_form}}

{{form.persons}}

-

- - {{form.rating}} -

{{form.ingredients}} diff --git a/recipes_app/views.py b/recipes_app/views.py index 08e29f8..ae78285 100644 --- a/recipes_app/views.py +++ b/recipes_app/views.py @@ -1,5 +1,5 @@ from django.shortcuts import render, redirect -from .models import Recipe, Topic +from .models import Recipe, Topic, Rating from .forms import RecipeForm import math from django.contrib.auth.decorators import login_required @@ -29,7 +29,7 @@ def home(request): recipes_count = recipes_filter.count() if sortval is not None: - recipes_filter = recipes_filter.order_by(sortdir + sortval) + recipes_filter = recipes_filter.order_by(sortdir + sortval).distinct() page_size = 50 num_pages = math.ceil(recipes_count/page_size) @@ -37,8 +37,8 @@ def home(request): recipes = recipes_filter[pgNr*page_size:(pgNr+1)*page_size] if num_pages > 0 else [] recipe_best = None - if num_pages>0: - rating_values = [r.rating for r in recipes] + if num_pages > 0: + rating_values = [r.rating_mean for r in recipes] recipe_best = recipes[rating_values.index(max(rating_values))] context = {"recipes": recipes, @@ -51,17 +51,11 @@ def home(request): def recipe(request, pk): recipe_from_key = Recipe.objects.get(id=pk) - recipe_fields = {getattr(recipe_from_key, field.name) for field in recipe_from_key._meta.get_fields()} - + # recipe_fields = {getattr(recipe_from_key, field.name) for field in recipe_from_key._meta.get_fields() if hasattr(recipe_from_key, field.name)} recipe_topics = recipe_from_key.topic.all() - persons_default = recipe_from_key.persons - if request.method == 'POST': - required_persons = request.POST.get("persons") - recompute_persons = True - else: - required_persons = persons_default - recompute_persons = False + required_persons = int(request.GET.get("persons")) if request.GET.get("persons") is not None else persons_default + recompute_persons = True if request.GET.get("persons") is not None else False ingredients_lines = [x.replace('\t', ' ') for x in recipe_from_key.ingredients.split('\n')] ingredients_formated = [] @@ -77,8 +71,34 @@ def recipe(request, pk): else: ingredients_formated.append((' ', x)) - context = {"recipe": recipe_from_key, "recipe_fields": recipe_fields, "ingredients_formated": ingredients_formated, - "required_persons": required_persons, "recipe_topics": recipe_topics} + if request.method == 'POST' and request.user.is_authenticated: + stars = int(request.POST.get("stars")) if request.POST.get("stars") is not None else 0 + body = request.POST.get("body") + if stars > 0: + ratings = recipe_from_key.rating_set.all() + has_rated = False + for r in ratings: + if request.user == r.user: + r.stars = stars + r.body = body + r.save() + has_rated = True + continue + if not has_rated: + Rating.objects.create( + user=request.user, + recipe=recipe_from_key, + stars=stars, + body=body, + ) + + ratings = recipe_from_key.rating_set.all() + recipe_from_key.rating_count = ratings.count() + recipe_from_key.rating_mean = sum([r.stars for r in ratings])/float(ratings.count() if ratings.count() > 0 else 1.0) + recipe_from_key.save(update_fields=['rating_mean', 'rating_count']) + + context = {"recipe": recipe_from_key, "ingredients_formated": ingredients_formated, + "required_persons": required_persons, "recipe_topics": recipe_topics, "ratings": ratings} return render(request, 'recipes_app/recipe.html', context) diff --git a/static/images/example_3.jpg b/static/images/example_3.jpg new file mode 100644 index 0000000..13f1396 Binary files /dev/null and b/static/images/example_3.jpg differ diff --git a/static/images/example_4.jpg b/static/images/example_4.jpg new file mode 100644 index 0000000..ea49e89 Binary files /dev/null and b/static/images/example_4.jpg differ diff --git a/static/images/example_5.jpg b/static/images/example_5.jpg new file mode 100644 index 0000000..b51e293 Binary files /dev/null and b/static/images/example_5.jpg differ diff --git a/user_app/migrations/0001_initial.py b/user_app/migrations/0001_initial.py index 9454c68..5be449b 100644 --- a/user_app/migrations/0001_initial.py +++ b/user_app/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.7 on 2024-08-24 14:16 +# Generated by Django 5.0.7 on 2024-08-27 14:03 import django.contrib.auth.models import django.contrib.auth.validators