Wyświetlanie szczegółów w relacji many-to-many

0

Witam,

pracuję nad projektem swojej pierwszej aplikacji w django, coś w rodzaju książki z przepisami, gdzie dodatkowo będzie można ułożyć meal plan
na dany dzień.

Mam problem z poprawnym wyświetleniem widoku szczegółów meal planu. Chodzi mi konkretnie o to, aby wyswietlila sie nazwa posiłku (np.śniadanie) i dalej dzialajacy link do przypisanego do tego posilku przepisu . Nazwa posilku jest polem 'meal' z opcja choice z modelu through, oto moje modele w calosci:

class Recipe(models.Model):
title = models.CharField(max_length=50)
cooking_time = models.IntegerField(help_text='w minutach', validators=[MinValueValidator(1), MaxValueValidator(5000)])
difficulty_level = models.IntegerField(choices=DIFFICULTY_LEVELS, default=1)
description = models.TextField()
created = models.DateTimeField(auto_now_add=True)
cuisine = models.ForeignKey('Cuisine', on_delete=models.CASCADE, null=True)
ingredient = models.ManyToManyField(Ingredient, through='IngredientRecipe')
meal_plan = models.ManyToManyField('MealPlan', through='RecipeMealPlan')

class RecipeMealPlan(models.Model):
    recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
    meal_plan = models.ForeignKey('MealPlan', on_delete=models.CASCADE)
    meal = models.IntegerField(choices=MEALS)

MEALS = (
    (1, 'Śniadanie'),
    (2, 'Drugie śniadanie'),
    (3, 'Obiad'),
    (4, 'Podwieczorek'),
    (5, 'Kolacja'),
    (6, 'Przekąska')
)

class MealPlan(models.Model):
    name = models.CharField(max_length=50)
    amount = models.IntegerField(validators=[MinValueValidator(4), MaxValueValidator(6)])

Oto mój widok stworzony do wyświetlania szczegółów meal planu:

  class MealPlanDetailsView(View):
def get(self, request, id):
    mealplan = MealPlan.objects.get(id=id)
    recipes = mealplan.recipe_set.all()
    return render(request, 'diet_app/mealplan_details.html', {'mealplan': mealplan, 'recipes': recipes})

Oraz html (wiem, że to tutaj coś nie gra):

 {% extends 'diet_app/base.html' %}
{% block title %}{{ mealplan|upper }}{% endblock %}
{% block content %}
                <h2>{{ mealplan|upper }}</h2>
                <ul> <p>Posiłki:</p>
                {% for recipe in mealplan.recipemealplan_set.all %}
                    <li>{{ recipe.get_meal_display}}: <a href="/recipe/{{recipe.id}}/">{{ recipe }}</a></li>
                {% endfor %}
                </ul>
{% endblock %}

Wszystko wyświetla się ok, natomiast nie działa link do szczegółów posiłku w meal planie, czyli

<a href="/recipe/{{recipe.id}}/">

Link działa, kiedy pętlę w html przerobię jak niżej:

{% for recipe in recipes %}
<li><a href="/recipe/{{recipe.id}}/">{{ recipe.title }} </a></li>
{% endfor %}

Ale wtedy nie wyświetla mi się nazwa konkretnego posiłku przy przepisie, a sam link z przepisem(który ładnie działa).

Nie umiem sobie złożyć tego w całość, aby wyświetały się równocześnie nazwa posiłku, a po niej działający link do przepisu.

Udało mi się tylko osiagnąć działający efekt umieszczając 2 powyższe pętle for razem, ale wtedy mam powtórzone kilka razy te same posiłki, co jest bez sensu.

Będę wdzięczny za jakieś podpowiedzi.

1

Pole meal masz jako IntegerField wiec będziesz dostawał na froncie Inta, zamiast choice ktorego wrzuciłeś.
https://docs.djangoproject.com/en/4.0/ref/models/fields/#choices
Zeby pobrać nazwe musisz zrobic np: recipe.get_recipe_display

For each model field that has choices set, Django will add a method to retrieve the human-readable name for the field’s current value. See get_FOO_display() in the database API documentation.
0

@szok: Chodzi mi o trochę co innego. Ja nazwę posiłku potrafię wyświetlić:

screen.jpg

Natomiast nie działa tam link z przepisem i mam problem z tym, aby to działało. Kod szablonu z htmla do takiego widoku wygląda u mnie obecnie tak:

{% extends 'diet_app/base.html' %}
{% block title %}{{ mealplan|upper }}{% endblock %}
{% block content %}
                <h2>{{ mealplan|upper }}</h2>
                <ul> <p>Posiłki:</p>
                {% for recipe in mealplan.recipemealplan_set.all %}
                    <li>{{ recipe.get_meal_display}}: <a href="/recipe/{{recipe.id}}/">{{ recipe }}</a></li>
                {% endfor %}
                </ul>
    <button type="button" class="btn btn-primary btn-lg"><a href="{% url 'add-recipemealplan' id=mealplan.id %}">
        Dodaj posiłek do meal planu</a></button>
    <button type="button" class="btn btn-primary btn-lg"><a href="{% url 'delete-mealplan' pk=mealplan.pk %}">
        Usuń meal plan</a></button>
{% endblock %}

I tutaj muszę coś poprawić z pętlą for, tak mi się wydaje..

0

Ok, czyli django zły link generuje do tej strony? Pokaż definicje urls.py dla tego widoku.

0

@szok: To jest urls.py dla tego widoku:

path('mealplan_details/<int:id>/', MealPlanDetailsView.as_view()),

Natomiast według mnie to tkwi w pętli. Jeśli rozpiszę ją inaczej:

{% for recipe in recipes %}
<li><a href="/recipe/{{recipe.id}}/">{{ recipe.title }} </a></li>
{% endfor %}

to linki ładnie działają, ale tak jak pisałem wcześniej - za cholerę nie mogę przy takim sposobie zapisu pętli dobić się do tego, żeby
wyświetlało się to pole z nazwą posiłku z relacji many to many. Dlatego właśnie przerobiłem pętlę zamiast:

  {% for recipe in recipes %}

dałem

    {% for recipe in mealplan.recipemealplan_set.all %}

Ale to z kolei powoduje to o czym rozmawiamy - czyli nie działa wówczas link z przepisem.

0

Dla tego kodu:
{% for recipe in mealplan.recipemealplan_set.all %}
Podejrzyj sobie co jest w recipe,
NIe podam Ci gotowca :)
zobacz tak co przekazujesz do szablonu jako recipe (jaki queryset?)
A jaki queryset jest generowany w szablonie dla mealplan.recipemealplan_set.all
Czeski błąd ... :) Na pierwszy rzut oka, pobierasz różne dane :)

0

@szok: Wydawało mi się, że wiem, co masz na myśli, ale po godzinie prób stwierdzam, że jednak nie. Mam 3 kombinacje, ale w każdej jakiś element szwankuje. Niemniej jednak dzięki za zaangażowanie i pomoc, będę jeszcze próbował

0

Bo w jednym przypadku pobierasz: mealplan.recipemealplan_set.al czyli recipemealplan_set
A w drugim jak przekazujesz do szablonu to odpalasz: recipes = mealplan.recipe_set.all() czyli samo recipe_set :)

1

@szok: Dzięki za odpowiedź, już wcześniej udało mi się rozwiązać to zapisem:

{% for recipemealplan in mealplan.recipemealplan_set.all %}
<li>{{ recipemealplan.get_meal_display}}: <a href="/recipe/{{ recipemealplan.recipe.id }}/">{{ recipemealplan }}</a></li>

ale trochę posiedziałem :)

0

@python_student: A skoro już rozmawiamy to może mógłbyś zerknąć na jeden z testów jakie piszę do swojej apki. Chodzi o test widoku edycji składnika (test dodania składnika udalo mi się zrobić.).

To mój widok i jego url oraz model:

class IngredientUpdateView(LoginRequiredMixin, UpdateView):
    model = Ingredient
    fields = '__all__'
    template_name_suffix = '_update_form'
    success_url = '/ingredients_list'

path('update_ingredient/<int:pk>/', IngredientUpdateView.as_view(), name='update-ingredient'),


class Ingredient(models.Model):
    name = models.CharField(max_length=50, unique=True)
    nutrient = models.IntegerField(choices=NUTRIENTS)
    glycemic_index = models.IntegerField(choices=GLYCEMIC_INDEX)

Od razu mówię, że to moje pierwsze testy jakie piszę i robię to trochę metodą prób i błędów, jeszcze nie do końca umiem sobie to
ułożyć w głowie.

Test jaki napisałem do tego widoku to:

@pytest.mark.django_db
def test_ingredient_update_view(client, example_ingredient):
    url = reverse('update-ingredient', kwargs={'id': example_ingredient.id})
    response = client.get(url)
    obj = Ingredient.objects.get(example_ingredient.id)
    obj.name = 'else'
    obj.save()
    assert response.status_code == 302

jest też fixtura:

@pytest.fixture
def example_ingredient():
    ing = Ingredient.objects.create(name='gruszka', nutrient=1, glycemic_index=2)
    return ing

Otrzymuję błąd

NoReverseMatch: Reverse for 'update-ingredient' with keyword arguments '{'id': 7}' not found.

Wcześniej w innych testach często miałem błąd wynikający z tego, że dla częśći widoków zastosowalem LoginRequiredMixin. Wtedy po prostu dorzucałem
argument typu 'user' do testu. Ale to nie jest ten typ błędu i trochę się plączę

1 użytkowników online, w tym zalogowanych: 0, gości: 1